NIPC.LIBRARY

NIPC is a new library designed to provide a high level
interface to networking hardware.  By using NIPC, programmers
will not have to deal with all of the issues involved in
networked communications such as sequencing, reliability, etc.
NIPC has also been designed to be both easy to use and yet very
powerful.

The NIPC API was designed in such a way that it would seem
familiar to Amiga programmers that are familiar with Exec
Messages and Device I/O.  Many NIPC function calls such as
GetTransaction(), DoTransaction() and ReplyTransaction()
strongly resemble function calls already present in the
Amiga OS.

At the lowest level, NIPC uses standard SANA-II device
drivers so that it is capable of running on virtually any
networking hardware, including Arcnet, Ethernet and even serial
ports via SLIP.

NIPC transfers data between machines with primarily two
concepts - "Entities" and "Transactions".

Entities and Transactions

An Entity is nothing more than a communications "endpoint". Any
data sent using NIPC is sent between two of these endpoints -
between two Entities.  An Entity is known by two characteristics:
it's name (an ASCII string of up to 63 characters plus a
terminating NULL), and the name of the machine on which it
resides.   These Entities are guaranteed uniqueness by the fact
that NIPC does not allow two Entities to exist on the same
machine with the same name.

A given machine can have an unlimited number of Entities, each
communicating with an unlimited number of other Entities. NIPC
allows communication between Entities by establishing
communications "paths" between individual Entities.  These paths
are considered unidirectional in the sense that only the Entity
that created the communications path can initiate a data
transfer.  However, the "paths" are also bidirectional in the
sense that data can flow in both directions.  In the diagram
below, four Entities named 'A', 'B', 'C' and 'D' exist with
various lines connecting the Entities together.  Each of the
lines indicates one of these data paths.  In the case below,
Entity A has a path to Entity C and also has a path to B.  B has
a path to D and also a path back to A.


         -                         -
        |A|   ---------------->   |B|
         -    <----------------    -
         |                         |
         |                         |
         |                         |
         |                         |
         V                         V
         -                         -
        |C|                       |D|
         -                         -

A data path is created by one Entity attempting to locate, or
'find' another Entity.  This is done with the library function
FindEntity().  Conversely, a data path is removed by the library
function LoseEntity().  These data paths exist only as long as
the creator of the path wishes it to exist (or until one side of
the path disappears for some reason).

Data is transferred from one point to another in Transactions. A
Transaction is defined by a structure called the Transaction
structure.  These are allocated and freed by the library
functions AllocTransaction() and FreeTransaction().

A Transaction is always initiated by a program that has
previously created a data path using the FindEntity() function.
The transaction is then sent to a destination Entity, which deals
with it however it wishes. The transaction is then returned to
the sender.  As said before, these data paths are somewhat
unidirectional.  In the above diagram, Entity A is allowed to
send a Transaction to Entity C - who will receive it and send it
back.  However, since Entity C has no data path to Entity A - it
cannot initiate a Transaction.  However, both Entity A and Entity
B established seperate data paths to each other allowing either
to initiate a Transaction to the other.

A Transaction does allow for bidirectional data flow, however.
The sender of a Transaction can attach "Request data" to
it that the receiver can read.  The receiver is allowed to
place data into a "Response buffer" and return that data back to
the sender.

A Transaction can at any time be in one of three phases: the
Request phase, the Servicing phase, and the Response phase.

The following diagrams may be useful in understanding each of
these phases.  In the diagram below, one machine is about to
communicate with another machine.  A program on machine 1 has
succeeded at a FindEntity(), thereby locating and connecting to
Entity B on Machine 2.  Now that the program has found Entity B,
it attempts to begin a Transaction by using either
BeginTransaction() or DoTransaction().  Once the program has
Begin/DoTransaction()'d, NIPC transmits the Request data portion
of the Transaction to Machine 2.  This phase of the Transaction
is the Request phase, and is shown in Diagram 1.

When the Transaction is received by NIPC on Machine 2, it is
placed onto the destination Entity - Entity B in this example.
While it remains on this Entity, the Transaction remains in the
Request phase.  Once the server program on Machine 2 attempts a
GetTransaction() on that Entity and removes the given Transaction
from the Entity, the Transaction enters the Servicing phase.
This is shown in Diagram 2.

While the server program attempts to fufill the needs of the
Transaction the Transaction remains in the Servicing phase.  When
the server program has finished with the Transaction and attempts
to return it to Machine 1 by calling ReplyTransaction(), the
Transaction enters the Response phase.  The response data is
transmitted back to the original sender (Machine 1) and the
Transaction is returned to the source Entity (Entity A) as shown
in Diagram 3.  The Transaction remains in this phase forever - or
until it is reused.  The trans_Type field indicates which phase a
given Transaction is in.


     MACHINE 1                            MACHINE 2
      ______                               ______
     |Entity|        Request Data         |Entity|
     |  A   |        ------------->       |  B   |
     |______|                             |______|
                    Request Phase
                      Diagram 1.


     MACHINE 1                            MACHINE 2
      ______                               ______
     |Entity|                             |Entity| (Server has
     |  A   |                             |  B   |  Transaction)
     |______|                             |______|
                    Servicing Phase
                      Diagram 2.


     MACHINE 1                            MACHINE 2
      ______                               ______
     |Entity|       Response Data         |Entity|
     |  A   |        <------------        |  B   |
     |______|                             |______|
                    Response Phase
                      Diagram 3.





Analogies and Similarities

NIPC shares many concepts with Exec messages and device
I/O.  One such concept is the idea that applications
communicate with other applications or devices by sending and
receiving finite pieces of data, usually in the form of an Exec
Message. NIPC also shares the concept of a communications
endpoint similar to the Exec Message Port.

In NIPC, the communications endpoint is known as an Entity.
Entities are used as both the source and destinations of data
being sent between two applications.  The data being sent between
applications is known in NIPC as a Transaction.

Shown below are two diagrams that illustrate how Messages and
Ports relate to Transactions and Entities.

Application<-->MsgPort<----Message----->MsgPort<-->Application

Application<-->Entity<---Transactions--->Entity<-->Application

Unfortunately, it gets more complicated than the above diagram
can show.  Unlike Exec Messages, NIPC Transactions are not
guaranteed to arrive at a destination Entity in a preset amount
of time.  In fact, it is possible that a Transaction will never
make it to it's destination due to machine and/or network
failures.  NIPC does make a good effort at delivering
transactions, however.  The lower level protocols used in NIPC
help to provide this functionality.

Another difference between NIPC communications and Exec Messages
is that NIPC is designed around a client-server model. In many
cases the two applications communicating will be a client and a
server, such as the case with a print spooler or a network
filesystem.  Because of this, clients have more control over the
transactions than the servers do.  This will become apparent
later on.




Using NIPC

Before communication via NIPC can begin, each application that
will be involved must create an Entity.  This is done by calling
CreateEntity().  Once an application has created it's own entity,
it must find the other application's entity.  Typically, the
entity to be found is a public Entity created by a server.

To find an Entity created by another program, you call
FindEntity().  FindEntity() has parameters that allow you to
specify the name of the Entity, the name of the host that you are
trying to find the Entity on, and the Entity that you will be
using for the source of your Transactions.

The reason that FindEntity() also requires a pointer to your
program's Entity is that NIPC communication is connection
oriented.  This means that a connection forms between your Entity
and the Entity you found.   This connection will continue to
exist until you no longer need it or until a permanent network
failure or machine failure occurs.  When you are done
communicating with the other Entity, you call LoseEntity().  The
concept of a connection does not exist with Exec Messages.  This
is why you must use a Forbid()/Permit() pair when communicating
with public message ports to guarantee that the port does not go
away in between the time you call FindPort() and PutMsg().

Once you have created your entity and found the remote entity,
you must allocate a Transaction structure.  The only supported
way of doing this is to call the NIPC function
AllocTransaction().

AllocTransaction() allows you to optionally specify buffer sizes
for any data being sent and/or any data that may be received that
the Transaction is complete.  You may also allocate your own
buffers, but you are responsible for freeing them yourself.

A Transaction is sent on it's way by calling either
BeginTransaction() or DoTransaction().  BeginTransaction() will
return immediately unless you are trying to send more data than
the network protocols are capable of handling at the moment. Once
your Transaction has begun, BeginTransaction() will return. It
should be noted that the amount of time that BeginTransaction()
will block will usually be quite small. Therefore,
BeginTransaction() should be considered an asynchronous call.

DoTransaction() will block until either the Transaction has
completed or an error occured.  Because your program will not be
able to anything until DoTransaction() returns, it is strongly
suggested that you try to avoid it, or spawn a separate task or
process to call DoTransaction().

When the Transaction returns, you should examine it to determine
how much, if any, data was returned and whether or not an error
condition occured.

Programs acting as servers usually do not call
AllocTransaction(), BeginTransaction() or DoTransaction().
Typically, a server will wait for a Transaction to arrive at it's
public Enitity.  This may be done by using either WaitEntity() or
by using the Exec Wait() function.  To see if a Transaction is
available at your Entity, you should call GetTransaction() which
will eithe return a pointer to a Transaction or NULL if there are
not more transactions to be processd.

For each transaction you receive, you should examine it to
determine what you should do with the data in it, or if you need
to return some data to a client.  Once you have finished
processing the transaction, you must return it to it's sender by
calling ReplyTransaction().

For an example NIPC client and server, please see the two
files named RemoteRequest.c and RequestServer.c.  These two
programs allow you to put up a requester on a remote machine
running NIPC.

Structure and Function call Overview

struct Transaction
{
        struct Message trans_Msg;
        struct Entity *trans_SourceEntity;
        struct Entity *trans_DestinationEntity;
        UBYTE          trans_Command;
        UBYTE          trans_Type;
        ULONG          trans_Error;
        ULONG          trans_Flags;
        ULONG          trans_Sequence;
        APTR           trans_RequestData;
        ULONG          trans_ReqDataLength;
        ULONG          trans_ReqDataActual;
        APTR           trans_ResponseData;
        ULONG          trans_RespDataLength;
        ULONG          trans_RespDataActual;
        UWORD          trans_Timeout;
};


    trans_Msg

        Used internally by nipc.library.  Do not use or examine.

    trans_SourceEntity

        This is the entity from which the Transaction originated.  This
        is filled in by Begin/DoTransaction().  It may be examined by the
        responder.  The common use of this field is for a responder who
        has just received a Transaction from another Entity to pass into
        GetHostName() or GetEntityName() - to determine the name of the
        Entity that sent the Transaction, and the name of the machine
        from which that Transaction originiated.

    trans_DestinationEntity

                Used internally by nipc.library.  Do not use or examine.

    trans_Command

        This field is filled in by the originator of the Transaction and
        may be examined by the responder.  It is generally only read by
        the responder.  This field is generally used to indicate the
        particular action the originator desires the responder to
        perform.  It is analogous to the IORequest io_Command field.
        Note that, unlike with Exec Device I/O, there are no standard
        responder commands which all requesters can expect all responders
        to understand.

    trans_Type

        One of TYPE_REQUEST, TYPE_RESPONSE or TYPE_SERVICING.  A
        Transaction is always in one of three phases - the Request phase,
        between the time it is Begin/DoTransaction()'d and the time the
        server GetTransaction()'s it from it's destination Entity; the
        Servicing phase, between GetTransaction()'ing it, and
        ReplyTransaction()'ing it; and lastly the Response phase, which
        begins when the Transaction is returned to it's Source Entity.
        The most common use of this field is to determine if a
        Transaction obtained from GetTransaction() is a Request from
        another Entity, or merely a returning Transaction that was
        originally sent from this Entity.

    trans_Error

        Both nipc.library and the user may write and examine this field.
        It is cleared (set to ENVOYERR_NOERROR) on a call to
        Do/BeginTransaction().  If the Transaction can be successfully
        copied locally or over the network, any value placed in this
        field by the responder will be here.  If the Transaction cannot
        be copied for some reason, nipc.library will place it's error
        code here.  Common error codes are defined in <envoy/errors.h>.
        Human-understandable text errors can be obtained from these error
        numbers with a call to envoy.library [NOT YET AVAILABLE].

    trans_Flags

        This field is a series of nipc.library flags.  These flags
        indicate several things, including who is responsible for freeing
        buffers.  Flags should not be set or cleared by the user except
        through NIPC functions or as permitted by comments in the
        Includes.

    trans_Sequence

        Used internally by nipc.library.  Do not use or examine.

    trans_RequestData

        Pointer to any data needed from the requester by the responder.
        This may be NULL if trans_ReqDataActual is zero, indicating that
        no request data was necessary.  The responder should make no
        changes to data in this buffer!  Programs which do will behave
        differently in networked vs. local Transactions.

    trans_ReqDataLength

        Length of the buffer pointed to by trans_RequestData.  It is
        eventually used by the allocator of the buffer to free the
        buffer.

    trans_ReqDataActual

        Length of data in the buffer pointed to by trans_RequestData
        which will actually be used by the responder.  This allows
        re-used Transaction structures to have large buffers which aren't
        copied across the network if the whole buffer isn't filled with
        data.  This may be NULL if no data is to be sent to the
        responder.

    trans_ResponseData

        Pointer to a buffer into which any reply will be placed by the
        responder.  This may be NULL if trans_RespDataLength is NULL.
        Currently this must be allocated by the requester, either at
        AllocTransaction() time or subsequently prior to
        Do/BeginTransaction().

    trans_RespDataLength

        Length of the buffer pointed to by trans_ResponseData.  It is
        eventually used by the allocator of the buffer to free the
        buffer.  The allocator of the buffer is never the responder.

    trans_RespDataActual

        Length of the data in the buffer pointed to by trans_ResponseData
        actually placed there by the responder.  This allows the
        responder to specify that the information it is returning is
        smaller than the buffer provided in order to avoid unnecessary
        copying unused buffer across the network.

    trans_Timeout

        This is the value in seconds that the requester expects as the
        maximum time for the responder to spend processing the request.
        nipc.library will add to this value the maximum time it expects
        the transfer of data between the Entities to take (this is
        variable depending on the distance of the machines
        communicating).  If a Transaction exceeds the total timeout and
        NIPC can abort the Transaction, it will be returned to the
        requester with trans_Error set to ENVOYERR_TIMEOUT.  Though no
        timeout will be used if trans_Timeout is set to zero, most
        Transactions should have a timeout.  If there is no timeout, a
        Transaction received by the responder but not replied (i.e.,
        because the machine crashed before getting to the reply) would
        never be returned (unless the requester called
        AbortTransaction()).  Thus, all programs should specify a Timeout
        other than zero or eventually call AbortTransaction() themselves.

        On the requester's side, Transactions are moved around with
        nipc.library functions which look and act much like Exec Device
        I/O functions.  Instead of DoIO(), WaitIO(), CheckIO(), AbortIO()
        and BeginIO(), nipc.library has DoTransaction(),
        WaitTransaction(), CheckTransaction(), AbortTransaction() and
        BeginTransaction().

    DoTransaction(dest_entity, src_entity, transaction)

        an easy-to-use Transaction function.  It initiates a Transaction
        and waits for its completion.  This is a synchronous function; it
        does not return until completion of the Transaction.  If the
        Transaction never completes, this call never returns.
        Non-trivial programs should avoid the use of this function in
        order to continue processing break signals or other user input.
        Use BeginTransaction() and Wait() on the Entity's signal bit
        instead.

    BeginTransaction(dest_entity, src_entity, transaction)

        initiates a Transaction without waiting for completion.  This is
        an asynchronous Transaction; control returns immediately.  Use
        GetTransaction() on src_entity to get the replied Transaction.

    transaction = GetTransaction(entity)

        Gets replied Transactions off of your source Entity.  You should
        use Wait() or WaitTransaction() rather than continuously polling.
         Once woken up from a Wait(Transaction)(), you should loop on
        GetTransaction() while it returns something other than NULL to
        get all Transactions off the Entity.

    WaitTransaction(transaction)

        Waits for the completion of a previously initiated asynchronous
        Transaction.  This function will not return control until the
        Transaction has completed (successfully or unsuccessfully).
        Programs should not WaitTransaction() on a Transaction that has
        not actually been sent.

    status = CheckTransaction(transaction)

        Examines a previously queued Transaction, and returns information
        on whether it has completed or not.

    AbortTransaction(transaction)

        attempts to cancel a Transaction.  AbortTransaction() may fail;
        if it succeeds, the Transaction is replied by NIPC earlier than
        it may otherwise have been replied. Programs must wait for the
        reply before actually reusing the Transaction. AbortTransaction()
        takes no action on Transactions which are already completed.

        The responder's side of a Transaction uses functions which are
        similar to those that manipulate Exec Ports and Messages.  Rather
        than WaitPort(), GetMsg() and ReplyMsg(), there are WaitEntity(),
        GetTransaction() and ReplyTransaction().

    WaitEntity(localentity)

        Waits for a Transaction to arrive at an Entity.  Non-trivial
        programs should avoid using this function in order to continue
        processing break signals or other user input.  Use Wait() on the
        Entity's signal bit instead.

    transaction = GetTransaction(entity)

        Transactions must be removed from Entities on the responder's
        side of a Transaction with a call to GetTransaction().

        Once a responder is done processing a Transaction, it must return
        the Transaction to the requester using the ReplyTransaction()
        function.

        Unlike Exec Ports which can be created manually or with an
        amiga.lib function, Entities must be created and destroyed with
        nipc.library or services.library functions.

    myentity = CreateEntity(tag1, ...)

        creates a new Entity which will can be used for calls to
        WaitEntity() and GetTransaction().  Entities created with
        CreateEntity() must eventually be deleted with DeleteEntity().

    DeleteEntity(entity)

        deletes an Entity created by CreateEntity().  Do not use on
        Entities found with FindEntity() (see below).

        Although the Entity structure is not publicly defined, two
        functions are provided to return some information about a given
        Entity.

    success = GetHostName(entity, stringptr, available)

        provides the name of the host on which a given Entity was
        created.  This is useful to responders that need to know which
        machines they are responding to.

    success = GetEntityName(entity stringptr available)

        provides the name of the Entity.  This is primarily useful to
        responders that need to know the name of the Entities to which
        they are responding.

    remoteentity = FindEntity(host, entityname, src_entity, errptr)

        Finds an Entity created by CreateEntity().  This Entity may be
        used as the destination of a Do/BeginTransaction().  Entities
        found with FindEntity() must eventually be forgotten with a call
        to LoseEntity().

    LoseEntity(entity)

        Forgets about an Entity found with a call to FindEntity().

