; IPXTEST.TXT -- Documentation for IPXTEST, IPXLIB version 0.20 demo program
; by Allen Brunson  06/01/94


Introduction
------------

IPXTEST is a fairly basic program that nonetheless demonstrates all the
procedures necessary to make IPXLIB work effectively.  It allows up to 30
users at PCs on a single network segment to exchange messages in real-time
(but could be made to support a more or less unlimited number by changing the
define USERTOTAL in IPXTEST.H).  It displays a large quantity of messages
about its operation so as to give the potential IPXLIB user a feel for what
is going on.  It is far from bulletproof; a great many optimizations and
"sanity checks" that would have gone into a production program have been
omitted to keep the source code relatively simple.


Compiling IPXTEST
-----------------

The following source files pertain solely to IPXTEST:

 IPXTEST.C    Main IPXTEST source file
 IPXTEST.H    Defines for IPXTEST
   INPUT.C    Keyboard input and input processing routines
 NETWORK.C    High-level IPX routines
 PROCESS.C    Miscellaneous processing routines
   VIDEO.C    Rudimentary screen functions

Like any other IPXLIB-using program, IPXTEST uses the following files:

   IPXSC.LIB  IPXLIB routines for the Small and Compact memory models
     IPX.H    Defines for IPXLIB
  IPXCFG.H    Configuration parameters for IPXLIB
 IPXUTIL.C    Optional routines for dealing with IPXADDRFULL structures

Create a project or make file that includes all the .C files and the proper
.LIB file.  IPXTEST was designed as a small model program, but can be made to
work in any memory model by including a different .LIB file and changing the
memory model check at the top of IPXTEST.C.

IPXLIB and IPXTEST have been tested with Borland's Turbo C++ v1.0 and v3.0
and Microsoft's C++ v8.0.  It works fine with all of these, except that the
Tiny model doesn't work with Microsoft C for some reason.  The program will
link okay but then crashes when run; also, it contains a lot of relocation
information (which I know my library didn't put there) which prevents the
program from being turned into a .COM file.  I suspect the problem has to
do with my understanding of the compiler; a more experienced Microsoft C
user could probably get it to work.

While tweaking the program to work with Microsoft C, I couldn't find library
functions to do certain things I wanted to do, so rather than writing my
own functions, I just left those parts out.  Specifically, when compiled
with a Microsoft compiler, IPXTEST will not be able to scroll the upper
part of the screen (it simply erases it instead), it won't wait a variable
length of time before sending ping responses (see below), and it is not
able to set screen colors.

An already compiled version of the demo is given as IPXTEST.EXE.


Running IPXTEST
---------------

IPXTEST should be run on two or more PCs on the same network.  (Currently,
all PCs must be on the same network segment.  This limitation will be
removed in a later version of IPXLIB.)  Each PC must at minimum have the IPX
TSRs loaded; it doesn't matter whether NETX or VLM is loaded or not.  If
you do have a "full" network, including a NetWare server, it might be easiest
to copy IPXTEST.EXE into a subdirectory on the server and run it from there.
On the other hand, you might wish to copy IPXTEST.EXE to each individual
PC's hard disk and unload NETX or VLM, just to convince yourself that IPXLIB
really does work without a server.

When started, IPXTEST draws a box around most of the screen, leaving a single
line free at the bottom for entering commands; this last line is where the
cursor is located.  If your display is in a text mode that has more than 25
lines, IPXTEST will adjust itself accordingly; since it can display up to
several lines of information for each packet transaction, it needs all the
lines it can get.  The utility LINE50.COM is included to put VGA displays
into 50-line mode (or EGAs into 43-line mode); you can run this before
IPXTEST if you wish.

IPXTEST first calls ipxStart().  If the IPX driver isn't loaded, you'll get
an error message to that effect; the program can't continue.  If the IPX
driver was located in memory, IPXTEST will display the local PC's network
address, a reminder that F1 will produce a help screen, and instructions to
use the NAME and PING commands to get started.

You can press F1 or enter the command HELP (or H) at any time to get a list
of commands and keys recognized.

Enter "NAME Allen" (without the quotes), replacing my name with yours.
The program will tell you that it has set your name.  Then enter PING, which
will cause IPXTEST to broadcast an "are you there?" request to all users.
Other copies of IPXTEST will respond with "Yes, I'm here" packets, sending
the name of the person along with the message.  IPXTEST waits for a random
amount of time up to one second before sending "Yes, I'm here" responses to
keep the requester from being deluged with replies all arriving at the exact
same instant.

You should get messages onscreen indicating received ping responses.  Wait
at least a second for the activity to die down.  Then enter DISPLAY to
show a list of all users that were found and the IPX addresses of their PCs.
The numbers in the "Rcv" and "Snd" columns indicate the maximum number of
send and receive ECBs that each machine has ever used at any given moment.
The machine you are using is always first in the list and indicated by an
asterisk.

The simplest command you can use is BROADCAST [message], which sends a
message to all users that the program knows about.  (Despite the name, it
does NOT broadcast the message to address FFFFFFFFFFFFh; this is considered
a sloppy programming technique.)  Note that this has the effect of updating
all receivers' user lists with your name and ECB usage statistics, if their
information is out of date.

The MESSAGE [usernum] [message] command will send a message to only one user.
You must enter the user's number from the list generated by DISPLAY.

An interesting feature of IPXTEST is that it will try to interpret the text
of a received broadcast message or directed message as if it were a command.
If it's not a valid command, no error will be displayed; it is assumed the
message was only informational.  If the message is a valid command, then
the receiving copy of IPXTEST will act on it.  Therefore, all running copies
of IPXTEST can be controlled from any one copy.

The FLURRY command is used to send out flurries of packets.  FLURRY ON will
begin the packet flurry.  If at least one copy of IPXTEST is sending
flurries, then all PCs that it knows about will receive the flurries and
will display the total number of flurry packets received every time that
1000 of them have arrived.  Try issuing the command BROADCAST FLURRY ON,
which will send the command to all PCs running IPXTEST, and see how badly
your network bogs down.  FLURRY OFF turns off the flurry packets;
BROADCAST FLURRY OFF will instruct all PCs to stop.  FLURRY RESET resets
the number of received flurry packets to zero.

STAT displays the maximum number and total number of send and receive ECBs.
(The maximums are also displayed in the DISPLAY listing.)  You can reset
these values to zeroes with STAT RESET.

If one user exits IPXTEST, it doesn't send any kind of notification to other
users that it has left, so their user tables will be out of date.  Issuing
another PING will clear things up.  This could have been taken care of by
the program itself, but this is only a demo, after all.

When you're finished playing with IPXTEST, enter QUIT to return to DOS.


Dissecting IPXTEST
------------------

There are several source files necessary for IPXTEST, but almost all of them
involve typical program tasks like printing text to the screen, managing
and processing user input, and so on, and can be safely ignored by the
reader who merely wants to learn how to use IPXLIB.

IPXTEST.H contains defines for IPXTEST, including the definition of the
USER structure that holds information on all known users, and struct
IPXTESTPKT, which is the structure for the data in all packets that IPXTEST
sends and receives.

VIDEO.C contains rudimentary screen functions.  It has routines to do tedious
video tasks like drawing the box around the main window, scrolling the top
portion of the screen, and so on.  All video output to the top window is done
via calls to message().

INPUT.C is responsible for reading keyboard input and processing command
lines once the user has finished entering them.  Two of its procedures,
getKeys() and cmdProcess(), are in the program's main loop.

PROCESS.C contains miscellaneous procedures not very interesting to the
potential IPXLIB user.  The one IPX-related procedure it contains is err(),
a routine that will display a message onscreen for each possible IPXLIB
error code.

NETWORK.C is the real heart of IPXTEST.  For the reader interested in
learning how to use IPXLIB, this is the file to look at.  It is initialized
with a call to netStart(), which calls ipxStart() to start up IPXLIB.  If
the define DEBUG in IPXTEST.H is un-commented, then netStart() passes the
address of an IPXDATA structure, ipxData, to ipxStart() to use as the
communication data.  This is useful for debugging, as you can simply
inspect the many fields of the structure to see what is going on.  If DEBUG
is not defined, then netStart() calls malloc() to allocate a block of memory
to use for communication data.

Next netStart() calls ipxAddrLocal() to get the address of the PC it is
running on and saves the address as the first entry in its user structure.
Then it copies its address to ipxAddrBroad, an IPXADDRFULL, and uses the
utility routine ipxAddrBrd() to set the node and immediate address fields
to FFFFFFFFFFFFh, which makes the address suitable for broadcasting.

Here is IPXTEST's main() procedure, from IPXTEST.C:

byte main(void)                                    // Begin main()
  {
    demoStart();                                   // Start up subsystems

    while (!endProgram)                            // Main program loop
      {
        getKeys();                                 // Get input keys
        cmdProcess();                              // Process commands
        while (ipxRecvChk()) recvPacket();         // Process packets
        sendFlurry();                              // Send flurry packets
        sendErr();                                 // Check for send errors
      }

    demoStop();                                    // Stop subsystems

    return FALSE;                                  // Return errorlevel 0
  }                                                // End main()

I made it intentionally simple.  Notice that it merely calls demoStart()
and then calls five major procedures in loop until endProgram is TRUE, then
calls demoStop().

The procedure getKeys() processes no more than one key at a time.  This
allows other tasks to run while the user is entering command lines.  The
procedure cmdProcess() will return immediately unless the user has just
pressed Enter, ending a command line, or unless there is a message to be
interpreted as a command.  If a command line is ready, cmdProcess() will
parse it and call the procedures necessary to execute it.

The procedure recvPacket(), in NETWORK.C, processes one received packet.
Note that the main loop is written so as to call recvPacket() as many
times as necessary to clear out all received packets.  This slows down
other processes somewhat but ensures that the receive ECBs should very
seldom completely fill up.

recvPacket checks the first word in the packet's data, looking for IPXTEST's
signature.  If it doesn't find it, it displays an error message and does
nothing further with the packet.

If the signature is okay, it checks the packet type (the second word in
the packet) to see if it's a flurry packet.  If it is, it increments the
count of flurry packets.  If the count is evenly divisible by 1000, it
displays the flurry packet total.

If it's not a flurry packet, it displays a message saying that it got the
packet, along with the address of the sender.

Since the signature in the packet was correct, the sender has now been
verified as a bona-fide IPXTEST user, so userSave() is called to add this
user to the user table (or to update the existing information).  The user's
address, name, and statistics are saved.

IPXTEST uses only five types of packets: ping, ping response, broadcast,
message, and flurry.  A number indicating the packet type is stored in
the packet.  Based on this information, recvPacket() calls one of four
routines to process the packet in some way (flurry packets have already
been dealt with).

If the packet was a broadcast or message, then recvBroadcast() or
recvMessage() is called, respectively.  These packets are quite easy to deal
with; these procedures simply print "Broadcast from <name>:" or "Message from
<name>:" and then the text of the message, which is stored as the fourth and
final field in the data packet.  Then the text of the message is copied to
cmdStr, the command string, and inputFlag is set to 2, which means "remote
input received."  The next time through the main loop, cmdProcess() will
notice this and try to interpret the message as a command.

Receiving a ping packet requires the most processing, and is done by
recvPing().  Since the ping will be received by possibly a great many other
copies of IPXTEST, all of which are going to send back a ping response, the
sender could be deluged with packets, its receive buffers might fill up
before it could process all the replies, and so some replies might be lost.
To keep this from happening, recvPing() waits for a random amount of time, up
to a second, before calling ipxSendPkt() to send a reply to the address that
the ping was received from.  Note that when IPXTEST sends a ping, it receives
a copy just the same as everybody else, so this procedure checks to see if
the sending address is the same as this PC; if so, it doesn't send a ping
response.

If the packet received is a ping response, recvPingResponse() is called.
The purpose of a ping response is to make note of the sender in the user
table, and recvPacket() has already done this, so recvPingResponse() does
nothing but print a message onscreen.

The procedure sendFlurry() is called in the main loop.  If flurry mode isn't
turned on, it does nothing.  If flurry mode IS on, it sends one (but ONLY
one) flurry packet to a user in the user table.  Then it increments its
user number for the next call.  If the current user number points to a
slot in the user table that is unused, sendFlurry() does nothing; given
that most of the slots in the 30-user table will be probably be free,
sendFlurry() wastes a lot of time.  This was done intentionally.

The procedure sendErr() is called repeatedly in IPXTEST's main loop.  It
checks for send errors with ipxSendChk(); if there is one, it uses
ipxSendErr() to retrieve the packet that couldn't be sent and the address it
should have been sent to.  If there's an error, sendErr() does nothing
but print a message; a "real" program should either retry the send or take
other action as necessary.

sendBroadcast() is used to broadcast messages to all users.  It does NOT
do so by sending the message to node address FFFFFFFFFFFFh, as you might
expect; it sends the message to each user in its user table instead.  This
is more work -- it has to do a send for every user in the table, instead of
just one send to the "broadcast" address -- but this is the proper way to
do it.  IPX broadcasts are received by every PC on the network, whether
they're running your program or not.  Therefore, each and every IPX driver
on every machine on the network will receive a broadcast and have to spend
precious CPU cycles determining that there isn't a program running at that
machine that wants it.  Therefore, you should only use broadcasts when
absolutely necessary, which is normally only while you're looking for other
PCs running your program.  If you fail to follow this rule, the network
managers at large sites where your program is run will be very unhappy with
you.

Since sendBroadcast() does a bunch of sends in quick succession, it really
should be written so that it checks the return value from ipxSendPkt() to
see if all available send ECB/packet pairs are in use, and if so, it should
wait a short period of time, say 20 milliseconds, perhaps using the time
to call ipxSendErr() to clear out any errors, and then try again.  It doesn't
do this as written.

sendMessage() is pretty much like sendBroadcast().  It's a little simpler
in that it only has to send to one user, and a little more complicated in
that it has to collect a user number and check to make sure that the number
is valid.

sendPing() is called when the user issues the PING command.  It clears its
user tables of all entries (except the PC its running on, of course) and
then broadcasts a ping packet.  A "ping packet," for the purposes of this
program, is nothing but one that has its packet type set to the numeric
value that means "ping packet."  Broadcasts should be avoided wherever
possible, because every PC on a network segment will receive them, and the
IPX driver at each PC will have to process them whether that PC has a running
program that wants the packet or not.  In this case a broadcast must be used,
since at this point it is not known who the other users are, so they can't
be targeted individually.


Thoughts on Robust IPX Programs
-------------------------------

There are a lot of things that can go wrong when using IPX.  You really
should take these things into account when designing an IPX-using program.

Among the most serious of IPX programming issues, and one that IPXTEST
doesn't tackle at all, is the fact that IPX doesn't guarantee delivery.
In the context of IPXTEST, a lost packet could mean that a broadcast message
doesn't arrive at all users' PCs, or that a ping response might be lost and
so someone's user table would be incorrect.  This isn't very serious in a
demo, but it could be quite disastrous in some production programs.

To make the process of keeping track of users more bulletproof, it would be a
good idea to have the program send a message to all users just before it
terminates.  Then they could all take that user out of their user table
without having to re-ping to clear things up.

When pinging, it would be advisable to broadcast a ping, wait a second or two
to receive replies, and then broadcast a second and third ping.  Given IPX's
claimed delivery rate of 95 percent, the odds against missing somebody that
way are quite remote.

To go even further, a ping probably shouldn't first clear out the user table,
but instead mark everyone as "suspect."  When a reply is received from
someone, that makes them a user in good standing.  If one or more PCs in the
table still haven't been heard from at the end of three pings, it's a good
bet that the user in some way exited the program without its being able to
send a message that it was terminating.  An error message stating that a
certain user has been "lost" might be appropriate.

When sending messages, it would be a good idea for the receiver to send back
a reply saying, "yes, I got it," perhaps also sending a checksum of the
message.  If the sender doesn't get a reply in a reasonable amount of time,
within a second or so, then it can display an error message stating that
the message wasn't received.  The amount of time to wait before timing out
depends on the network itself.

Another serious issue is the fact that IPX packets may not arrive in the
same order you sent them in.  They probably will, especially on small
networks, but in larger networks there may be more than one route between
any two PCs, and based on network traffic or other criteria, a bridge might
send a packet one direction one time and another direction the second time;
this can certainly lead to out-of-order packets.

Therefore, if you're sending a message that comprises more than one packet,
all packets should contain some kind of sequence number.  That way the
receiver can reconstruct the message as necessary.

Network traffic can vary greatly from network to network and from minute
to minute.  Yours will almost certainly not be the only program running
on the network.  As you are writing your program, if at all possible take
into account the fact that network throughput will not remain constant, and
keep network traffic to a minimum as much as possible.

Some sort of handshaking will be necessary to get faster machines to
properly communicate with slower machines.  Chances are excellent that a
fast '486 machine will be able to send enough packets to a slow '286 to
make its receive ECBs fill up very quickly.



Configuring IPXLIB for IPXTEST
------------------------------

IPXLIB is configured for a specific program via defines in IPXCFG.H.  The
defines were not changed to reflect the needs of IPXTEST because I wanted to
leave them at reasonable values for most programs.  However, it will be
instructional to consider how these values should have been set for this
program.

The value of IPXSOCKET should of course be set to some semi-random value
between 4000h and 7FFFh, just as with any other IPX-using program.  The user
wishing to make her program particularly bulletproof would set the socket
number in a configuration file, read it in during program initialization, and
use it to set the ipxStart() parameter "socket."

The value IPXHOPS should be set to something small, since this is a demo; 2
would probably be a good value.  (This setting is currently unused anyway;
non-beta versions will correct this.)

IPXDATASIZE is an easy one.  The largest packet that IPXTEST ever sends
contains 92 data bytes, when doing a message broadcast or a message send
(ping packets and responses and flurries are smaller), so IPXDATASIZE
should be set to 92.

IPXRECVCNT should be set to a fairly large number, since flurries can
obviously fill them up very quickly, especially when fast machines send to
slower ones.  Since the size of each individual packet buffer is very
small, and there's no performance penalty imposed by having lots of
ECB/packet pairs, this number should be set very high, probably all the
way up to the maximum, 250.

IPXTEST only generates multiple back-to-back sends under one circumstance:
when broadcasting a message to all users.  So the program might have to do as
many as 29 back-to-back sends.  But it's a fair bet that most sends will
complete within microseconds of making the request, so setting IPXSENDCNT
to 10 should be high enough.
