eval 'exec perl -x $0 ${1+"$@"}' # -*-perl-*-
  if 0;
#!perl -w
#
# ======================================================================
# This file is Copyright 1998,1999 by the Purdue Research Foundation and
# may only be used under license.  For terms of the license, see the
# file named COPYRIGHT included with this software release.
# AAFID is a trademark of the Purdue Research Foundation.
# All rights reserved.
# ======================================================================
#
# AAFID::Comm package.
#
# AAFID project, COAST Laboratory, CERIAS, 1998-1999.
# 
# Diego Zamboni, Jan 23, 1998.
#
# $Id: Comm.pm,v 1.30 1999/09/03 17:08:52 zamboni Exp $
#
# NOTE: This file is in Perl's POD format. For more information, see the 
#       manual page for perlpod(1).
#
# Change log is at the end of the file.
#

package AAFID::Comm;

# The following keeps up with the RCS version number. The self-assignment
# keeps the -w switch from complaining (because $VERSION may not be used
# here)
$VERSION = do { my @r = (q$Revision: 1.30 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; $VERSION = $VERSION;

use strict;
use Carp;
use Sys::Hostname;
use IO::File;
use IO::Select;
use Socket;
use IO::Socket;
use Data::Dumper;
use AAFID::Message;
use AAFID::Log;
use AAFID::Config;
use Comm::Reactor;

use subs qw(
	    sendMsgUp
	   );

use vars qw(
	    $CONTROLLERENTITY_PORT
	    $buf
	    $buf_pos
	    @buf_lines
	    %buf_handle
	    $BUF_READSIZE
	   );

	    # Initialization (at time of loading the package)
	    BEGIN {
		$Data::Dumper::Indent=0;
		$Data::Dumper::Terse=1;
		my %c=AAFID::Config::configure;
		Log_register("debug", $c{logfile});
		foreach (@{$c{logcategories}}) {
		  Log_activate($_);
		}
		$CONTROLLERENTITY_PORT=$c{listenport};
		$buf="";
		$buf_pos=0;
		@buf_lines=();
		$BUF_READSIZE=4096;
	    }
=pod

This package tries to encapsulate all the mechanisms behind the inter-entity
communications on the AAFID system. However, in the current implementation,
much of the mechanism is still handled by the AAFID entities, because they
keep track of their communication handles. This will be hopefully fixed
at some point.

=head1 Sending messages "up"

In AAFID, "up" is defined as the entity that controls (and sometimes that
started). With the entity "above", communication is performed through the
standard output and input channels, although handled properly through the
B<Comm::Reactor> package. The C<sendMsgUp> subroutine gets a message as
its first argument. If a second, non-zero argument is given, the message
is printed as is, without using the Reactor message format. This is
used when we are running standalone.

=cut 

sub sendMsgUp {
  my $msg=shift;
  my $standalone=shift;
  croak "Comm::sendMsgUp: Got a non-message argument: $msg"
    unless (ref($msg) eq "AAFID::Message");
  Log("debug", "sendMsgUp('".$msg->toString."')\n");
  if (!$standalone) {
    # If not standalone, use the regular message format.
    Log("debug", "Sending using send_message\n");
    Comm::Reactor::send_message(Comm::Reactor::stdout, $msg->toString);
  }
  else {
    # If standalone, just print it.
    my $fh=Comm::Reactor::stdout();
    Log("debug", "Standalone, printing normally to stdout.\n");
    print $fh $msg->toString."\n";
  }
}

=head1 Sending messages to an arbitrary destination

Simply receives the message to send and a file handle (that should be
open already), and prints the message to it.

=cut

sub sendMsgTo {
  my $msg=shift;
  croak "Comm::sendMsgTo: Got a non-message argument!"
    unless (ref($msg) eq "AAFID::Message");
  my $to=shift;
  croak "Comm::sendMsgTo: undefined handle"
    unless $to;
  my $standalone=shift;
  # Check if the handle is stdout. If it is and the third optional
  # argument is given (which means we are running standalone),
  # simply print the message instead of sending it through Reactor.
  if ( ($to == Comm::Reactor::stdout() || fileno($to) == fileno(STDOUT) ) &&
       ($standalone) ) {
    Log("debug", "Sending message to stdout and I'm standalone, so printing regularly\n");
    print $to $msg->toString."\n";
  }
  else {
    Comm::Reactor::send_message($to, $msg->toString);
  }
}

=head1 Sending arbitrary strings.

Similar to C<sendMsgTo>, but receives as argument a string, and sends
it as is.

=cut

sub sendStringTo {
  my $msg=shift;
  my $to=shift;
  croak "Comm::sendStringTo: undefined handle"
    unless $to;
  my $standalone=shift;
  # Check if the handle is stdout. If it is and the third optional
  # argument is given (which means we are running standalone),
  # simply print the message instead of sending it through Reactor.
  if ( ($to == Comm::Reactor::stdout() || fileno($to) == fileno(STDOUT) ) &&
       ($standalone) ) {
    Log("debug", "Sending string to stdout and I'm standalone, so printing regularly\n");
    print $to "$msg\n";
  }
  else {
    Comm::Reactor::send_message($to, $msg);
  }
}

=head1 Receiving messages

Messages can be received from any number of handles. The generic routine
C<nextMsg> takes two optional arguments: a timeout and an B<IO::Select>
object, and tries to get the next available message from either standard
input or the handles stored in the B<IO::Select> object (in fact, if
STDIN is not in the Select object, it will be added to it). If the timeout
is not given or its value is C<undef>, the call blocks forever, until
a message becomes available. If its value is zero, the call returns
immediately with C<undef> if no message was available. Otherwise, it
specifies the time (in seconds, with fractional values allowed) to wait
for a message to become available before returning C<undef>.

If the message came from standard input, the special field C<_FROMSTDIN>
is set to 1. Also, unconditionally, the reference to the file handle where
the message came from is stored in the special field C<_FROMHANDLE>.

C<nextMsg> uses non-buffered input to read from the handles. This is
to allow the select mechanism to work correctly. However, this also
means that we cannot use Perl's line-oriented input
(C<E<lt>HANDLEE<gt>>), which is a buffered mechanism. Thus, C<nextMsg>
does buffering internally. All the available input is read from each
handle, split into lines, and stored in C<@buf_lines>, in the order
they were received. Each element of C<@buf_lines> is a reference to a
two-element array whose first element contains the line read, and the
second one contains a reference to the file handle from which the line was
read. This allows the retrieval of the next line received by simply
shifting the first element of C<@buf_lines>.

Additionally, we need a way of getting the next line read from a specific
file handle. To this effect, the C<%buf_handle> hash contains pointers to
the corresponding lines. C<%buf_handle> is indexed by filehandle 
reference (interpreting it as a string), and each element contains a
list of references to the corresponding elements in C<@buf_lines>.
When a line is read off C<@buf_lines>, its second element is set
to C<undef>. This way, when the next line from a specific handle is
requested, the corresponding element of C<%buf_handle> can be searched
for the first element that has a non-C<undef> value in its second
position.

All of this is handled through three subroutines:

=over 4

=item C<bufferInput>

Reads all available input from all available filehandles, and stores
it in C<@buf_lines> and C<%buf_handle> as appropriate. Returns
C<undef>, not matter what was read.

=item C<nextMsg>

Returns the next available message. If there are no buffered lines,
calls C<bufferInput>. If there are buffered lines, it returns the
first one, interpreted as a message, and does not call C<bufferInput>

If an argument is given to C<nextMsg>, it is interpreted as a file
handle reference, and the next message from that file handle is
returned.

=item C<nextLineStruct>

Returns the next available line in a reference to a three-element
array that contains the line itself in the first position, a reference
to the file handle from which the line was read in the second
position, and a flag that indicates whether the line was read from
STDIN in the thirs position. If there are no buffered lines, calls
C<bufferInput>. If there are buffered lines, it returns the next one,
and does not call C<bufferInput>.

If an argument is given to C<nextLine>, it is interpreted as a file
handle reference, and the next line from that file handle is
returned.

=item C<nextLine>

Same as C<nextLineStruct>, but returns only the string that was read,
or undef on no input. The newline is stripped from the end of the string.

=back 4

=cut 

sub bufferInput(;$$) {
  my $timeout=shift; 
  my $sel=shift; 
  my $temporary=0; 
  my $any_msgs=0; 
  # Both arguments may be empty, so if the $sel object is empty, we 
  # have to create a temporary one.
  unless ($sel) {
    $sel=IO::Select->new(), $temporary=1;
    Log("debug", "bufferInput: creating temporary select object\n");
  }
  # If STDIN is not in the $sel object, put it in, so that we can
  # by default at least read messages from STDIN.
  unless ($sel->exists(\*STDIN)) {
    $sel->add(\*STDIN);
    Log("debug", "bufferInput: adding STDIN to the select object\n");
  }
  # Now $sel has at least STDIN, plus any others that may have been
  # already there.
  while(1) {
    my @ready=$sel->can_read($timeout);
    my $fh;
    foreach $fh (@ready) {
      Log("debug", "bufferInput: Reading from: " . Dumper($fh) . "\n");
      # Check if the handle is STDIN.
      my $isSTDIN=(fileno($fh) == fileno(STDIN));
      Log("debug", "bufferInput: Handle is STDIN\n") if $isSTDIN;
      
      # If the filehandle is a socket in LISTEN mode, do the
      # accept and add the data socket to the select object.
      if (!$isSTDIN && ref($fh) && 
	  $fh->isa("IO::Socket") && isServerSocket($fh)) {
	my $new=$fh->accept;
	$new->autoflush(1);
	$sel->add($new);
	# The server socket can be either an internet socket or a
	# Unix domain socket, so we check to print the appropriate
	# log message.
	if ($fh->sockdomain == AF_INET) {
	  Log("debug", "Got new connection on server socket, from ".
	      scalar(gethostbyaddr($new->peeraddr, AF_INET)) . "\n");
	}
	elsif ($fh->sockdomain == AF_UNIX) {
	  Log("debug", "Got new connection on server socket, at ".
	      $new->hostpath . "\n");
	}
	else {
	  Log("errors", "Got a connection on an unknown socket domain: ". 
	      $fh->sockdomain . "\n");
	}
      }
      else {
	my $m;
	
	# Do an initial read.
	# $buf, $buf_pos and $BUF_READSIZE are package-global variables.
	$m=sysread($fh, $buf, $BUF_READSIZE, $buf_pos);
	
	# If we get a close on the handle, we remove it from the $sel
	# object, and if it was STDIN, return -1. Otherwise (a close on
	# any other handle), do nothing else.
	if ($m==0) { 
	  # If we get 0 on the first read, then it's EOF
	  Log("debug", "bufferInput: got channel close\n");
	  $sel->remove($fh);
	  if ($isSTDIN) {
	    Log("debug", "bufferInput: it was STDIN\n");
	    addLines(\@buf_lines, $fh, $isSTDIN, undef);
	    $any_msgs=1;
	  }
	}
	else {
	  do {
	    $buf_pos+=$m;
	  } while ( ($m >= $BUF_READSIZE) &&
		    ($m = sysread($fh, $buf, $BUF_READSIZE, $buf_pos))
		  );
	  my @lines=split(/\n/, $buf);
	  # If we get an empty list, it means the input contained only
	  # newlines, which were stripped and ignored by the split.
	  unless (@lines) {
	    @lines = ("") x length($buf);
	  }
	  if (chop($buf) ne "\n") {
	    $buf = pop(@lines);
	    $buf_pos = length($buf);
	  }
	  else {
	    $buf = "";
	    $buf_pos = 0;
	  }
	  addLines(\@buf_lines, $fh, $isSTDIN, @lines);
	  $any_msgs=1;
	}
      }
    }
    # If we get here, we want to check if whatever was returned in the
    # @ready array was only EOFs on non-STDIN handles or new
    # connection requests on a server socket. If so, we want to check
    # if we were asked to block. If blocking is desired, simply
    # loop back. If not, return with undef.
    # If any messages were returned, then we return @list_of_msgs.
    if ($any_msgs) {
      return undef;
    }
    if(defined($timeout)) {
      #	    Log("debug", "bufferInput: timeout provided, exiting\n");
      $sel->remove(\*STDIN) if $temporary;
      return undef;
    }
    else {
      #	    Log("debug", "bufferInput: blocking desired, looping back\n");
    }
  }
}

=pod

The <addLines> subroutine buffers lines read from a file handle. It
receives as arguments a reference to the array to add the lines to,
the file handle from which they were read, a flag indicating if the
file handle is STDIN, and the lines to add. For each one of them, it
creates a three-element array containing the line, the file handle,
and the C<$isSTDIN> flag. References to the three-element array are
stored both in C<@buf_lines> and in C<%buf_handle> under the element
for the appropriate file handle.

=cut

sub addLines {
  my ($arrayref, $fh, $isSTDIN, @lines)=@_;
  my @newelems=map { [$_, $fh, $isSTDIN] } @lines;
  push @buf_lines, @newelems;
  $buf_handle{$fh}=[] unless $buf_handle{$fh};
  push @{$buf_handle{$fh}}, @newelems;
}

sub nextLineStruct {
  my %args=@_;
  my $timeout=$args{Timeout};
  my $fh=$args{Handle};
  my $sel=$args{Select};
  my $line;
  my $tried_once=0;

  if (!defined($sel)) {
    $sel=IO::Select->new;
  }
  if ($fh && !$sel->exists($fh)) {
    $sel->add($fh);
  }

  LOOP_nextLine:
  while (1) {
    # If a specific file handle is given, we have to check that
    # something from that file handle is available.
    if ($fh) {
      while ($line=shift( @{$buf_handle{$fh}} )) {
	if (defined($line->[1])) {
	  # Copy element before setting the file handle to undef.
	  my @elem=@$line;
	  $line->[1]=undef;
	  return \@elem;
	}
      }
      if (!$tried_once) {
	bufferInput($timeout, $sel);
	$tried_once=1;
	next LOOP_nextLine;
      }
      else {
	return undef;
      }
    }
    else {
      while ($line=shift(@buf_lines)) {
	if (defined($line->[1])) {
	  # Copy element before setting the file handle to undef.
	  my @elem=@$line;
	  $line->[1]=undef;
	  return \@elem;
	}
      }
      if (!$tried_once) {
	bufferInput($timeout, $sel);
	$tried_once=1;
	next LOOP_nextLine;
      }
      else {
	return undef;
      }
    }
  }
}

sub nextLine {
  my $line=nextLineStruct(@_);
  if ($line) {
    return $line->[0];
  }
  else {
    return undef;
  }
}

# Callback for input from STDIN.
# This can be specified as a callback for a Comm::Reactor or Comm::Conn.
sub nextSTDINmsg {
  my ($fh, $line)=@_;
  my $msg;
  if (!defined($line)) {
    Log("debug", "nextSTDINmsg: got channel close on STDIN.\n");
    Comm::Reactor::remove_handle($fh);
    return -1;
  }
  Log("debug", "nextSTDINmsg: got line from STDIN: $line\n");
  $msg=AAFID::Message->new($line);
  if ($msg) {
    # So that the entity can figure out easily that the message came
    # from STDIN.
    $msg->{_FROMSTDIN}=1;
    # Store the handle where it came from in the message too,
    # for the caller to be able to add it to its tables if
    # necessary.
    $msg->{_FROMHANDLE}=$fh;
  }
  return $msg;
}

# Callback for input from any non-STDIN handle.
sub nextMsg {
# This can be specified as a callback for a Comm::Reactor or Comm::Conn.
  my ($fh, $line)=@_;
  my $msg;
  if (!defined($line)) {
      Log("debug", "nextMsg: got channel close.\n");
      Comm::Reactor::remove_handle($fh);
      return -1;
  }
  Log("debug", "nextMsg: got line: $line\n");
  $msg=AAFID::Message->new($line);
  if ($msg) {
    # Store the handle where it came from in the message too,
    # for the caller to be able to add it to its tables if
    # necessary.
    $msg->{_FROMHANDLE}=$fh;
  }
  return $msg;
}

=head1 Server sockets

Some entities need the ability to receive connection requests from
other hosts over the network. For this, they need to set up server
sockets. This is what the C<newServerSocket> subroutine does. If no
parameters are passed, it uses the default port number contained in
C<$CONTROLLERENTITY_PORT>. Otherwise, the argument is interpreted as
the port number to use. It returns an C<IO::Socket> object if the
creation is successfull, or C<undef> otherwise.

=cut

sub newServerSocket {
  my $port=shift || $CONTROLLERENTITY_PORT;
  my $sock=IO::Socket::INET->new(Listen		=> 10,
				 LocalAddr	=> undef,
				 LocalPort	=> $port,
				 Proto		=> 'tcp',
				 Reuse		=> 1
		     );
  # Pure perl hackery. Store an additional variable in the "hash side
  # of the GLOB" which is the socket, to indicate that this is our
  # server socket.
  $sock and $ {*$sock}{_AAFID_Comm_ServerSocket}=1;
  return $sock;
}

=pod

Associated to this last hack to signal the socket as a server socket,
we have the subroutine C<isServerSocket>, which examines that variable
and returns true or false.

=cut

sub isServerSocket {
  my $sock=shift;
  return undef if !$sock;
  return (ref($sock) and exists $ {*$sock}{_AAFID_Comm_ServerSocket});
}

=head1 Utilities

C<isSTDIN> returns true if the handle passed as argument is associated
to STDIN.

=cut

sub isSTDIN {
  my $fh=shift;
  return undef if !$fh;
  return (fileno($fh)==fileno(STDIN) || $fh==Comm::Reactor::stdin());
}

=pod

Similar for isSTDOUT.

=cut

sub isSTDOUT {
  my $fh=shift;
  return undef if !$fh;
  return (fileno($fh)==fileno(STDOUT) || $fh==Comm::Reactor::stdout());
}

1;

# $Log: Comm.pm,v $
# Revision 1.30  1999/09/03 17:08:52  zamboni
# Changed the start line to something that is path-independent, and
# updated the copyright notice.
#
# Revision 1.29  1999/06/09 06:22:37  zamboni
# - Changed LocalAddr from INADDR_ANY to undef in newServerSocket, because
#   it was causing problems in Linux, and apparently the module takes
#   care of assigning it to INADDR_ANY if it is undef.
#
# Revision 1.28  1999/06/08 05:01:53  zamboni
# Merged branch a06-raw-data-collection into main trunk
#
# Revision 1.27.2.1  1999/06/07 15:53:52  zamboni
# - Added error checking.
#
# Revision 1.27  1999/04/01 02:30:35  zamboni
# - Added checks for _standalone to sendStringTo.
#
# Revision 1.26  1999/03/31 07:22:48  zamboni
# - Added isSTDIN and isSTDOUT.
#
# Revision 1.25  1999/03/29 22:33:22  zamboni
# Merged branch a05-new-comm-module, which updates it to make use of the new event-based communication mechanism.
#
# Revision 1.24.4.3  1999/03/29 16:01:46  zamboni
# - Added sendStringTo()
# - Removed use of Comm::Conn
# - Modified sendMsgUp to print directly to STDOUT if running standalone.
# - Modified sendMsgTo to print directly to the handle if it is STDOUT and
#   running standalone.
# - Updated nextSTDINmsg and nextMsg to interpret undefined message as
#   close on handle.
#
# Revision 1.24.4.2  1999/03/19 17:08:54  zamboni
# - Modified sendMsgUp to use Comm::Reactor::stdout instead of creating
#   its own STDOUT handle.
#
# Revision 1.24.4.1  1999/03/18 00:42:56  zamboni
# - Made the BEGIN block initialize Comm::Conn
# - Modified sendMsgTo and sendMsgUp to use Comm::Reactor::send_message
#   instead of directly printing to the file handle.
# - Added nextSTDINmsg routine for processing messages coming from standard
#   input.
# - Modified nextMsg to receive arguments as a callback for a
#   Comm::Reactor or a Comm::Conn.
#
# Revision 1.24  1998/09/07 17:35:19  zamboni
# Added support for filters!
#
# * Monitor.pm: Replaced all uses of AAFID::Message->new by uses
#   of $self->newMsg.
#
# * Filter.pm: Cleaned up the code and the comments, and added some
#   extra features, such as default versions of makefield and
#   makeline that make it easier to define new filters if they read
#   data where the fields are space-separated. Also, added some
#   error checks here and there.
#
# * Entity.pm: Added filter support
#   - Added descriptions for FiltersNeeded and FilterPaths parameters.
#   - Modified getParameter so that if a second argument is given, it is
#     used to initialize the parameter in case it is undefined.
#   - Added subroutines connectFilters, newFilterClientSocket,
#     setFilterPattern
#   - Added subroutine newMsg.
#
# * ControllerEntity.pm: Added filter support.
#   - Some general cleanup (removing stray commented code, etc.)
#   - Rewrote some sections for clarity (in invoke, _invoke,
#     _instantiate_and_run)
#   - Modularized loadmodule. Created new subroutines:
#       _loadmodule_error
#       _loadmodule_success
#   - Modularized _instantiate_and_run, creating new subroutine
#       _fork_and_run.
#   - Added the setupFilters subroutine, which takes care of loading
#     all the filters required by an entity. It is complemented by
#     an augmented message_CONNECT, which detects when a filter is
#     connecting and does the necessary updating.
#
# * Comm.pm: Removed an annoying debug message from bufferInput.
#
# * Agent.pm: Added code to contact the filters.
#
# Revision 1.23  1998/08/27 16:41:37  zamboni
# - Fixed a bug in bufferLines that ignored the input when it consisted
#   only of newlines.
#
# - Made nextLine return the text of the next line, and created a new
#   nextLineStruct that returns the three-element array that was previously
#   returned by nextLine.
#
# - Fixed a bug in bufferLines that used the wrong socket to determine
#   the socket type when a connection on a server socket was received.
#
# - Made nextLineStruct robust to when no Select object is passed.
#
# Revision 1.22  1998/08/26 22:36:55  zamboni
# Reimplemented the reading mechanisms to use non-buffered I/O. This fixes
# problems that ocurred when mixing buffered I/O with the use of the Select
# object. Now the AAFID::Comm library keeps an internal buffer of lines
# read, and allows to retrieve them through nextMsg (interpreting them
# as AAFID messages, as usual) and through a new subroutine called nextLine,
# which returns the line read.
#
# Also, all the reading routines (nextMsg and now nextLine) return only
# the next available element. I had changed nextMsg so that it returned an
# array of messages, but that was a bad decision, I think, so we went back
# to the old behavior.
#
# Moved the RCS log to the end of each file.
#
# Now nextMsg and nextLine take named parameters for Timeout, Handle and
# Select object.
#
# Entity.pm and Monitor.pm were modified to go back to the behavior of Comm::nextMsg
# returning a single message. They had been modified to process a list of messages,
# but this was seen to be a bad decision.
#
# Also, calls to nextMsg were modified to make use of the new syntax, which requires
# named parameters.
#
# Revision 1.21  1998/08/11 16:23:22  zamboni
# - Made nextComm return a list of messages instead of only one message. Also
#   made it read all the available messages from any file handle, not only
#   the first one as before.
#
# Revision 1.20  1998/08/10 16:36:19  zamboni
# Removed a log message that used the peeraddr method of a socket, because
# it breaks when using Socket::UNIX objects, which do not have that method.
#
# Revision 1.19  1998/06/29 20:11:23  zamboni
# Added copyright message
#
# Revision 1.18  1998/06/27 04:23:59  zamboni
# Added a check for a null message in nextMsg, so that the _FROMSTDIN and
# _FROMHANDLE fields are not set unless we have a valid message.
#
# Revision 1.17  1998/06/26 21:08:49  zamboni
# Made it use AAFID::Config instead of AAFID::Constants to get the
# monitor listen port and to set log categories.
#
# Revision 1.16  1998/05/07 06:05:22  zamboni
# Removed debug messages by default.
#
# Revision 1.15  1998/04/14 07:28:20  zamboni
# *** empty log message ***
#
# Revision 1.14  1998/03/13 17:02:07  zamboni
# - Added $VERSION.
#
# Revision 1.13  1998/03/06 07:10:34  zamboni
# - Added subroutine newServerSocket.
# - Made nextMsg detect when a new connection request comes through a
#   server socket, and act accordingly by accepting the connection and
#   adding the new connection to the select object.
# - Added subroutine isServerSocket.
# - Added "use strict"
# - Corrected problems signales by strict.
# - Corrected printing of address where the connection comes from in
#   nextMsg, because it was being printed in binary format.
#
# Revision 1.12  1998/02/26 05:35:33  zamboni
# Made nextMsg store a reference to the filehandle from where the message came
# into the message itself, as field _FROMHANDLE.
# Changed the name of the FROMSTDIN field to _FROMSTDIN, to denote that it
# is an internal field.
#
# Revision 1.11  1998/02/25 07:26:43  zamboni
# Modified sendMsgUp to be able to take more than one message, and
# send them sequentially.
#
# Revision 1.10  1998/02/23 05:41:31  zamboni
# Works now. Now also temporary select objects in NextMsg (when no select
# object is passed) works.
#
# Revision 1.9  1998/02/20 04:33:38  zamboni
# *** empty log message ***
#
# Revision 1.8  1998/02/18 21:52:44  zamboni
# nextMsg now kind of works. It works ok if a Select object is passed as
# argument, but if not, the creation of the temporary select object causes
# a weird error on the second invocation. Have to check that.
#
# Revision 1.7  1998/02/18 20:14:28  zamboni
# Ditched all the previous version. The new communication design is much
# cleaner, but it makes almost all of the previous coding useless.
# Mainly, commands from "up" come through standard input, and messages sent
# "up" go to standard output. Messages can also come from other sources, but
# that will be taken care of by the entity. The communication library only
# receives the appropriate handles to send things to or to read things from.
#
# Revision 1.6  1998/01/27 19:21:50  zamboni
# Commented out debugging messages.
# Have to substitute them with a more sophisticated debugging facility that
# can be activated at run time.
#
# Revision 1.5  1998/01/26 20:56:23  zamboni
# nextMsg now also works.
# Removed unnecessary "use lib".
#
# Revision 1.4  1998/01/25 22:19:32  zamboni
# sendMsg, newNetId, and the other functions seem to work now.
#
# Revision 1.3  1998/01/24 21:10:56  zamboni
# Made some miscellaneous changes. Now getSocket() always returns a list
# instead of a scalar, and some cosmetic changes.
#
# Revision 1.2  1998/01/23 19:59:59  zamboni
# First version that compiles cleanly. Haven't tested it yet. :-)
#
# Revision 1.1  1998/01/23 19:29:11  zamboni
# Initial revision
#
