#!/usr/bin/perl
#
# $Id: dnclient.pl,v 1.5 2002/12/01 05:47:10 jclopez Exp $
#
# DN client script
# Language: Perl
#
# Copyright (C) 2002	Julio Lopez	All Rights Reserved.
# See disclaim.txt for disclaimer and copyright information.
#
# ----------------------------------------------------------------------------
# Usage: dnclient.pl <--port=number> [--infile=filename] [--outfile=name]
#		[--datafile==name]
#
# This script reads a packet description file and sends packets to a node in
# the document network.
#
# The valid command line arguments are:
# --port=number:      local port number to use for the client.  This is a
#		      required parameter.  Choose the local parameter using
#		      the following stratey: port = 19900 + group-number.
# --infile=name:      Optional parameter that specifies the name of the input
#		      file.  default = client.in
# --outfile=name:     Optional parameter that specifies the name of the output
#		      file.  default = client.out
# --datafile=name:    Optional parameter that specifies the name of the output
#		      file where additional output data is written.
#		      default = client.dat
#
# The configuration parameters are set in the client-config.pl file.
#
# The input file contains the description for the packets to generate.
# The format of the input file is as follows:
# - Each line contains a packet description.
# - Each line has only two fields: PACKET_TYPE document_name.
# - PACKET_TYPE can either be a number or a packet type name (i.e. LOOKUP,
#   QUERY, DATA_REQUEST).
# - document_name is the name of the document to include in the packet
# The rest of the fields in the packet are filled as follows:
# source_ip, source_port, dest_ip, dest_port: are set to 0.
# seq_num: is read from the client-config.pl file, and incremented for each
#	packet.
# type: as specified in the input file.
# TTL: default value from client-config.pl
# name_length: derived from the document name.
# name: the document name specified in the input file.
# data: none.
#
# Limitations:
# The client can only send the following types of packets:
# LOOKUP, QUERY, DATA_REQUEST.
# It can only receive the following types of packets:
# ERROR, LOOKUP_REPLY, REPLY, DATA_REPLY.
#
# The information about the received packets is written to the "client.out"
# file (or the --outfile parameter specified in the command line).
# ----------------------------------------------------------------------------
use Getopt::Long;
use Socket;

require "client-config.pl";

%packet_types =
  (
   "ERROR"	  => 1,
   "LOOKUP"	  => 2,
   "LOOKUP_REPLY" => 3,
   "QUERY"	  => 4,
   "REPLY"	  => 5,
   "DATA_REQUEST" => 6,
   "DATA_REPLY"	  => 7
  );

@packet_type_name =
  (
   "UNKNOWN",
   "ERROR",
   "LOOKUP",
   "LOOKUP_REPLY",
   "QUERY",
   "REPLY",
   "DATA_REQUEST",
   "DATA_REPLY"
  );


$linenum = 0;

# Global variables modified
# - INFILE
# - $line_num
# - $seq_num
# Read only global variables:
# - $source_ip
# - $dest_ip
# - $source_port
# - $dest_port
# - $packet_types
# - $ttl

sub get_next_entry() {
  my $entry, $line, $type_name, $name;

  if ($line = <INFILE>) {
    $linenum++;
    chomp($line);

    ($type_name, $name) = split(/[ \t]/, $line);

    if (defined($type_name)) {
      if ($type_name == 0) {
	$type = $packet_types{$type_name};

	if (!defined($type)) {
	  print STDERR "Not a valid packet type $type_name in line $linenum: "
	    . "$line\n";
	  next;
	}
      } else {
	$type = $type_name;
      }

      if (!defined($name)) {
	print STDERR "Entry in line $line did not contain a document name\n";
	next;
      }

      $nam_len   = length $name;

      # for debugging
      print "send: seq = $seq_num, type = $type_name ($type), ttl = $ttl, ".
	"name_len = $nam_len, name = $name\n";

      $entry = pack("NNnnnCCCa$nam_len", $source_ip, $dest_ip, $source_port,
		    $dest_port, $seq_num, $type, $ttl, $nam_len, $name);
      $seq_num++;
    }
  }

  return $entry;
}

sub get_ip_str($) {
  my $in_addr;

  $in_addr = pack("N", $_[0]);

  # Obtain the IP address string
  return inet_ntoa($in_addr);
}


sub get_error_data($$) {
  my $seq_num, $data, @stuff;

  @stuff    = unpack("Nnn", $_[1]);
  @stuff[0] = get_ip_str($stuff[0]);

  print "Error data: seq = $_[0], dest_ip = $stuff[0], dest_port = $stuff[1]"
    . " code = $stuff[2]\n";
  print DATAFILE "$_[0] $stuff[0] $stuff[1] $stuff[2]\n";
}

sub get_lookup_data($$) {
  my ($seq_num, $data, @stuff);

  @stuff  = unpack("NnC", $_[1]);
  @stuff[0] = get_ip_str($stuff[0]);

  print DATAFILE "$_[0] $stuff[0] $stuff[1] $stuff[2]\n";
}

sub get_reply_data($$) {
  my $seq_num, $data, $len, $i, $ip_addr, $port;

  $seq_num = shift;
  $data	   = shift;
  $len     = unpack("C", $data);
  $data    = substr($data, 1);

  print DATAFILE "$seq_num $len\n";
  print "seq = $seq_num, route_len = $len REPLY data follows:\n";

  for ($i = 0; $i < $len; $i++) {
    ($ip_addr, $port) = unpack("Nn", $data);
    $ip_addr	      = get_ip_str($ip_addr);
    print DATAFILE "$ip_addr $port\n";
    print "seq = $seq_num, node_address = $ip_addr $port\n";
    $data = substr($data, 6);
  }
}

sub get_data_reply_data($$) {
  my ($seq_num, $data);

  $len  = unpack("N", $_[1]);
  $data = substr($_[1], 4);
  $hex_len = $len*2;
  $hex_data = unpack("H$hex_len", $data);

  print DATAFILE "$_[0] $len $hex_data\n";
}


# Read from SOCK
# Writes to OUTFILE and DATAFILE

sub receive_packet() {
  my ($buf, $buf_len, $host, $host_inaddr, $hostname, $port, $source_ip,
      $dest_ip, $source_port, $dest_port, $seq_num, $type, $ttl, $nam_len,
      $name, $data, $type_name);

  $buf = "";
  $buf_len = 4096;

  $host = recv(SOCK, $buf, $buf_len, 0);

  if (!defined($host)) {
    print STDERR "Error in recv(): $!\n";
    return;
  }

  $buf_len = length($buf);

  ($port, $host_inaddr) = unpack_sockaddr_in($host);
  $hostname = inet_ntoa($host_inaddr);

  # Unpack the data
  ($source_ip, $dest_ip, $source_port, $dest_port, $seq_num, $type, $ttl,
   $nam_len) = unpack("NNnnnCCC", $buf);

  $name = substr($buf, 17, $nam_len);
  $data = substr($buf, 17 + $nam_len);

  $source_ip = get_ip_str($source_ip);
  $dest_ip   = get_ip_str($dest_ip);



  if ($type < @packet_type_name) {
    $type_name = $packet_type_name[$type];
  } else {
    $type_name = $type;
  }

  print "rec: seq = $seq_num, type = $type_name, name = $name\n";

 TYPE_SWITCH: {
    if ($type == 1) { get_error_data($seq_num, $data); last TYPE_SWITCH; }
    if ($type == 3) { get_lookup_data($seq_num, $data); last TYPE_SWITCH; }
    if ($type == 5) { get_reply_data($seq_num, $data); last TYPE_SWITCH; }
    if ($type == 7) { get_data_reply_data($seq_num, $data); last TYPE_SWITCH; }
  }

  print OUTFILE "$hostname $port $buf_len $source_ip $source_port $dest_ip " .
    "$dest_port $seq_num $type_name $type $ttl $nam_len $name\n";

  return;
}

# ----------------------------------------------------------------------------
# Entry point -> main
# ----------------------------------------------------------------------------

GetOptions("infile=s", "outfile=s", "port=i", "datafile=s");

if (!defined($opt_port)) {
  die("Please specify a local port number with the --port option\n");
}

$local_port = $opt_port;

if (defined($opt_infile)) {
  $infile = $opt_infile;
}

if (defined($opt_datafile)) {
  $datafile = $opt_datafile;
}


if (defined($opt_outfile)) {
#  print "opt_outfile $opt_outfile\n";
  $outfile = $opt_outfile;
}

# print "outfile=$outfile\n";
# print "infile=$infile\n";

open(INFILE, $infile)  or die("error: could not open $infile\n");
open(OUTFILE, '>'.$outfile)   or die("error: couldn't open $outfile\n");
open(DATAFILE, '>'.$datafile) or die ("error: couldn't open $datafile\n");

# create and bind the UDP socket (no SO_REUSEADDR)
socket(SOCK, PF_INET, SOCK_DGRAM, IPPROTO_UDP);
bind(SOCK, sockaddr_in($local_port, INADDR_ANY))
  or die("bind error: $!\n");

print "local_port   = $local_port\n";
print "node address = $node_ip:$node_port\n\n";

$node_ip_addr = inet_aton($node_ip) or die("$node_ip: $!");
# is the port in network or host byte order???
$node_addr = pack_sockaddr_in($node_port, $node_ip_addr);

$rin = "";

vec($rin, fileno(SOCK), 1) = 1;

$win = $ein = $rin;

$run = 1;
$more_entries = more;

while ($run) {
  $rout=$rin;
  $wout=$win;
  $eout=$ein;

  ($nfound, $timeleft) = select($rout, $wout, $eout, $timeout);

#  print "nfound = $nfound, timeleft = $timeleft, ($rout, $wout, $eout)\n";

  if ($nfound > 0) {
    if (vec($eout, fileno(SOCK), 1)) {
      print STDERR "Error in select() $!\n";
      last;
    }

    if (vec($rout, fileno(SOCK), 1)) {
      receive_packet();
    }

    if (vec($wout, fileno(SOCK), 1)) {
      $entry = get_next_entry();

      if (defined($entry)) {
	$nbytes = send(SOCK, $entry, 0, $node_addr);
	if (!defined($nbytes)) {
	  print STDERR "Error sending packet: $!\n";
	}
#	print DATAFILE $entry;
      } else {
	undef $win; # Don't check the write descriptor set anymore
	$moreentries = 0;
      }
    }
  } else {
    if ($timeleft == 0 && $moreentries == 0) {
      $run = 0;
      last;
    }
  }
}

close(INFILE);
close(OUTFILE);
close(DATAFILE);
