JiNX Network System (JNet) - IPX Network Driver - By Mark Hodson, 1998-99

This library is freeware.  The only provision is that the programme using this
code must display in its credits (if it has any) an acknowledgement of the use
and original source of this code.

ie. "JNet IPX Network System by Mark Hodson".


Introduction
~~~~~~~~~~~~
This programme is a reasonably simple and easy to use driver for communicating
over IPX networks with IPX packets (no SPX).

It abstracts significantly from the IPX driver installed on the system, making
network programming much simpler.

It is quite flexible, allowing you the user a choice in how you implement your
system, which itself will depend on what you are going to use it for.


Driver Philosophy
~~~~~~~~~~~~~~~~~
Throughout my travels in making a driver, I constantly thought "what is the
minimum amount of information I need to know?".  This was done for my own
benefit, as I love to go making complicated system-level code one week, only
to use it for a year and forget how on earth it works... of course then
something goes wrong with my code and I have to learn it all over again! :)

Well hopefully nothing will go wrong... go wrong... go wrong... and you will
enjoy the simple approach to IPX network programming!


Network Basics
~~~~~~~~~~~~~~
OK there are a few things you really HAVE to know.


-=< Network Addressing >=-

A full IPX network address is 12 bytes long.

Bytes 0 to 3 (4 of them) identify the LAN (Local Area Network) within perhaps
a WAN (Wide Area Network).  Each LAN is separated from its neighbours by
bridges or repeaters.  In most cases you will only be operating on a single
LAN (you and all your friends connected by some BNC or UTP cable), and you
will find this field is 00000000h.

Bytes 4 to 9 (6 of them) identify the NIC (Network Interface Card) within the
workstation/computer.  This code is inside some ROM on the network card, and
usually just identifies the computer, since usually there is only 1 NIC per
workstation.  In essence it identifies a connection to the LAN.  It might be
something like 00400622AC43h.

Bytes 10 to 11 (2 of them) are a 16bit socket number.  Sockets are dynamic
objects which get opened and closed run-time.  Sockets are senders and
receptors of information.  Sockets will only pickup packets off the network
which are meant for that socket, allowing you to screen out a lot of garbage
that might be floating around the network.  It is therefore important to
somehow know what socket number(s) to use to establish communications.


-=< JNet IPX Terminology >=-

I will term the 10 byte LAN-NIC identifier combination as a "network address".
This identifier is unique and static for each computer (infact each NIC).  The
other 2 bytes I will just call the "socket".


-=< JNet IPX Packets >=-

Packets are the bits of information which get sent over the network.  IPX has
its own special packet formulation which is what sets it apart from other
protocols.

You will not need to know much about packets, as the JNet IPX driver will
assemble them for you.

You do however need to know that the maximum packet size in IPX is (at least)
576 bytes.  Most IPX drivers allow packets up to 1000 or 1500 bytes, but in
the intrests of compatibility JNet IPX only allows packets up to 576 bytes.
Now 30 bytes of that is the IPX header, so you can only transfer 546 bytes of
data at a go.  You can if you like transfer 0 bytes of data or any amount in
between 0 and 546 bytes.

The IPX packet structure is defined in the C header file for one very good
reason.  You will need to read incoming IPX packets.  The header will tell you
from what address the packet originated and what socket - very important if
you want to reply.


How JNet IPX Handles Network Transactions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Some of the internals of IPX handling are messy, so I have abstracted away
from it all by creating objects called "Packet Handlers".  Each handler has
1 transaction and 1 packet associated with it.

A transaction is just what it is trying to do - either send or receive a
packet.  The packet it is sending/receiving is attached to the Packet Handler
and is part of it.

When you send a packet, a handler is devoted to the job.  When you wish to
receive a packet, a handler is devoted to that too.  The handler becomes
unavailable until it succeeds or an error occurs.

For this reason you will want several packet handlers to be available at any
one time.  If you wish to cue up 10 packets for sending, and also want to be
listening for up to 12 incoming packets, you will need at least 10+12+1 packet
handlers.  The extra handler is the one which has just been used successfully,
but is frozen for you to examine or extract its contents.

You should periodically check the status of network transactions using a Check
Status function.  This will either tell you nothing new has happened, or that
one of the packet handlers has completed its transaction.

The Check Status function returns a code telling you something of the
completion status of the packet - whether it was sent successfully, received
successfully, or if some sort of error occurred, what went wrong.

At this point you can examine the packet.  If a successful send occured you
can continue on.  If a successful receive occured you can remove the data and
perhaps source address for use.  If an error occured you may attempt some sort
of error recovery with a re-send or request for a re-send.

When you have finished with the frozen packet, you can call Check Status
again, which will destroy the frozen packet and continue checking other packet
handlers.

You should call Check Status in a loop, handling completed transactions, until
it returns nothing new.


The JNet Functions - IPX Packet Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The below functions are the core functions of the JNet IPX system.

-=< Initialise >=-

int JNet_IPX_Initialise(int number_of_packet_handlers);

1. Allocates enough locked realmode memory for the desired number of packet
   handlers.  Each handler takes up 640 bytes of realmode memory.

2. Ensures IPX is available on the system, and if so queries IPX for the
   driver's maximum packet size (unsigned short IPX_MaximumPacketSize) and the
   driver's retry count (unsigned short IPX_RetryCount).  This information is
   of little consequence to the JNet IPX interface.

3. Also queries IPX for your network address (char IPX_NetworkAddress[10]).

4. Returns a JNet Error Code, or 0 for no error.

eg. result = JNet_IPX_Initialise(32);


-=< Open Socket >=-

int JNet_IPX_OpenSocket(IPX_Socket socket);

1. Opens a socket for sending or receiving of data (or both at the same time).

2. If you specify a socket number 0x0000, then IPX will choose one for you in
   the range 0x4000 to 0x4FFF.  If your socket number is non-zero, IPX will
   attempt to open that socket.  Keep in mind many sockets have reserved
   purposes below 0x4000.  It is recommended you choose a socket number in the
   range 0x8000 to 0x8FFF if you wish to choose the socket yourself.

3. Returns a JNet Error Code, or 0 for no error.

4. If successful, the newly open socket will be recorded (IPX_Socket
   IPX_LastSocketOpened).  You should copy this socket value into a local
   variable as it will be overwritten if you attempt to open another socket.

eg. result = JNet_IPX_OpenSocket(0x8123); socket = IPX_LastSocketOpened;


-=< Close Socket >=-

int JNet_IPX_CloseSocket(IPX_Socket socket);

1. Closes a socket previously opened with Open Socket.  Supply the actual
   socket number as a parameter, not 0x0000 even if when opening the socket
   this is what you used (ie. choose a socket for me).

2. Any incomplete transactions on that socket will be cancelled, a fact that
   will be reflected by the appropriate status code if you call Check Status
   after closing the socket.

3. Returns a JNet Error Code, or 0 for no error.

eg. result = JNet_IPX_CloseSocket(socket);


-=< Broadcast Packet >=-

int JNet_IPX_BroadcastPacket(IPX_Socket socket, IPX_Socket dest_socket,
                             void *data, int data_length);

1. This sends a packet to all nodes listening on a particular specified
   socket.  I am unsure if IPX broadcasting will reach LANs other than your
   own in a WAN.

2. You specify the local socket you wish to send the packet with as well as
   the destination socket you wish to send to.

3. You also specify the data you wish to enclose in the packet, by means of a
   pointer to the data and its length in bytes.  Remember the maximum length
   of data you can send in a single packet is 546 bytes.

4. Returns a JNet Error Code, or 0 for no error.

eg. result = JNet_IPX_BroadcastPacket(socket, 0x8666, text, strlen(text)+1);

    This will be picked up by any people on the LAN who are listening on
    socket 0x8666.


-=< Send Packet >=-

int JNet_IPX_SendPacket(IPX_Socket socket,
                        char *dest_address, IPX_Socket dest_socket,
                        void *data, int data_length);

1. This sends a packet to a particular node and a particular socket on that
   node.  The node is identified by its network address.

2. Apart from the extra parameter identifying the destination node, this
   function is identical to Broadcast Packet.

3. Returns a JNet Error Code, or 0 for no error.

eg. char john[] = { 00h,00h,00h,00h,10h,33h,2Ch,F4h,EDh,1Ah };

    result = JNet_IPX_SendPacket(socket, john, 0x8666, text, strlen(text)+1);

    This will be picked up by the node specified by the 10 byte network
    address named "john", if it exists and is listening on socket 0x8666.

    Note the "usual" way of getting someone else's network address is by
    copying the source_address field of a broadcast packet, not by manual
    entry as above.


-=< Receive Packet >=-

int JNet_IPX_ReceivePacket(IPX_Socket socket);

1. Sets a packet handler listening on the specified socket.

2. The function does not wait for data to arrive; it returns control back to
   the host programme immediately.

3. If and when data does arrive on that socket the listener will accept the
   data.  When you next call Check Status it will notify you that a listener
   has completed its transaction, at which point you can extract the data.

4. Returns a JNet Error Code, or 0 for no error.

eg. result = JNet_IPX_ReceivePacket(socket);


-=< Relinquish Control >=-

int JNet_IPX_RelinquishControl(void);

1. This function can be called if you find you have some idle time, and wish
   to give some extra processing time to the IPX driver.

2. Since most drivers are interrupt driven this function is often redundant,
   so you can safely ignore it (ie. never call it).  It is called once every
   time you call Check Status anyway.

3. Returns a JNet Error Code, or 0 for no error.

eg. result = JNet_IPX_RelinquishControl();


-=< Check Status >=-

int JNet_IPX_CheckStatus(void);

1. This function should be called on a regular basis.  It looks through all
   packet handlers to see if any have completed their transaction.

2. Returns a JNet Status Code, or 0 if no new transactions have been
   completed.

3. If the return value is non-zero, the JNet system will report to you the
   effected packet (IPX_Packet *IPX_LastPacketReported).  The status code
   itself tells you what has happened to this packet.

   Status codes for packet handler which as completed a Receive transaction:

        Success!                0x10
        Cancelled               0x18
        Data Lost               0x19
        Bad Socket              0x1A
        Unknown Error           0x1F

   Status codes for packet handler which as completed a Send transaction:

        Success!                0x20
        Cancelled               0x28
        Data Malformed          0x29
        Packet Undeliverable    0x2A
        Hardware Failure        0x2B
        Unknown Error           0x2F

4. When the Check Status function reports to you a packet as above, the
   packet handler is ear-marked for destruction.  A subsequent call to
   Check Status will change the packet pointer (IPX_LastPacketReported) and
   destroy the related handler so it can be reused at a later time.  For this
   reason you must deal with each packet as it is reported to you.

eg. do {
        result = JNet_IPX_CheckStatus();

        if (result)
        {
            if (result == 0x10 || result == 0x20)
            {
                // Handle successful receive or send.
            }
            else
            {
                // Handle error.
            }
        }
    } while (result);


-=< How Many Spare >=-

int JNet_IPX_HowManySpare(void);

1. This function simply tells you how many packet handlers are spare at this
   time.  If there is a handler frozen by the Check Status function so you
   can examine its contents, then it will not be counted as spare.

2. Returns the number of spare packet handlers.

eg. printf("There are %0d spare packet handlers.\n", JNet_IPX_HowManySpare());


-=< How Many Receiving >=-

int JNet_IPX_HowManyReceiveing(void);

1. This function simply tells you how many packet handlers are devoted to
   receiving packets at the time of the call.

2. Returns the number of receiving packet handlers.

eg. printf("There are %0d receiving packet handlers.\n", JNet_IPX_HowManyReceiving());


-=< How Many Sending >=-

int JNet_IPX_HowManySending(void);

1. This function simply tells you how many packet handlers are devoted to
   sending packets at the time of the call.

2. Returns the number of sending packet handlers.

eg. printf("There are %0d sending packet handlers.\n", JNet_IPX_HowManySending());


The JNet Functions - Address Registry Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The address registry is a _very_ simple way of saving network addresses for
later retrieval.  You can store, retrieve but not delete addresses from the
registry.  This utility is probably insufficient for most moderately complex
network tasks, but you can make your own I'm sure!

-=< Initialise Registry >=-

int JNet_IPX_InitialiseRegistry(int number_of_entries);

1. This function allocates enough memory to hold the specified number of
   network addresses.  You should choose enough entries to hold the
   addresses of all the computers you wish to converse with simultaneously.

2. Returns a JNet Error Code, or 0 for no error.

eg. result = JNet_IPX_InitialiseRegistry(32);


-=< Checking and Registering Addresses >=-

int JNet_IPX_RegisterAddress(char *address);

1. Adds a 10 byte network address to the registry.

2. Returns an integer handle (0 or above) by which the address can be
   retrieved later, or -1 if there was insufficient space left in the
   registry.

int JNet_IPX_CheckAddress(char *address);

1. Returns a boolean (0 = false, 1 = true) denoting whether a particular
   address already exists in the registry.  Useful for avoiding adding the
   same address twice.

int JNet_IPX_HowManyAddresses(void);

1. Returns the number of addresses currently stored in the registry.


-=< Retrieving Addresses >=-

char *JNet_IPX_ReturnAddress(int index);

1. Returns the 10 byte network address of the registered entry accessed by the
   supplied integer index.  The index is the value returned when the address
   was registered.


Appendix A - JNet Error Codes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
No Error                        0x00

DPMI (Memory) Error             0x01

No IPX Driver on System         0x10
Unknown IPX Error               0x11

Socket Already Open             0x14
Socket Table Full               0x15
Socket Does Not Exist           0x16

Packet Handler Table Full       0x18
Packet Data Too Long            0x19


Appendix B - Implementation Notes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Delivery Order

There are no guarantees on order of delivery of IPX packets.  If you send two
packets, the later may arrive first.  Also thanks to the implementation of the
JNet Packet Handlers, this prospect becomes a whole lot more likely.

So if order of arrival is important, you will have to timestamp each packet
you send (use a simple 32bit incrementing counter).  Then in your check loop
when you deal with a received packet, copy its contents only if its timestamp
is greater than the last one you know of.

This is dead simple and fast to implement, and the only reason it is not
present intrinsically to the JNet IPX system is that in many applications it
is not necessary, and infact undesirable (you want all packets that have
arrived, not just the most recent of a whole bunch that have arrived).


2. The Demo Programme (IPX_XFER)

The supplied demo programme is an excellent working reference for how to
implement IPX communications.


3. Compiling

The pre-compiled libraries were compiled with Watcom V11, for stack and
register based calling.


