[net.sources.mac] Macintosh Internet Protocols

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

# use "sh" (not "csh") to unpack these archives
mkdir copyright
mkdir appl
mkdir t
mkdir net
mkdir sfmget
#
echo extracting copyright/notice.text...
cat >copyright/notice.text <<'!E!O!F!'
The Lisa Pascal distribution of MacIP is Copyright 1985 by
     Carnegie-Mellon University.  It is made available for
     public use without fee, and without warranties express
     or implied.  It is derived from the MIT version of the
     Internet protocols for the IBM PC, the copyright notice
     for which appears directly below.  Initial work on the
     MacIP package was done by Mark Sherman; his copyright
     notice is shown at bottom.	 The current version was done
     by Tim Maroney at C-MU.

Copyright 1983, 1984 Massachusetts Institute of Technology

Permission to use, copy, modify, and distribute this program
for any purpose and without fee is hereby granted, provided
that this copyright and permission notice appear on all copies
and supporting documentation, the name of M.I.T. not be used
in advertising or publicity pertaining to distribution of the
program without specific prior permission, and notice be given
in supporting documentation that copying and distribution is
by permission of M.I.T.	 M.I.T. makes no representations about
the suitability of this software for any purpose.  It is pro-
vided "as is" without express or implied warranty.

Copyright 1984 Mark Sherman

Permission to use, copy, modify, and distribute this program
for any purpose and without fee is hereby granted, provided
that this copyright and permission notice appear on all copies
and supporting documentation, the name of Mark Sherman not be used
in advertising or publicity pertaining to distribution of the
program without specific prior permission, and notice be given
in supporting documentation that copying and distribution is
by permission of Mark Sherman.  Mark Sherman makes no representations about
the suitability of this software for any purpose.  It is pro-
vided "as is" without express or implied warranty.
People acquiring, modifying or using this version of the
software are requested to let the author know by sending correspondence to:
	Mark Sherman
	Department of Mathematics and Computer Science
	Dartmouth College
	Hanover, NH 03755

	Mark.Sherman@CMU-CS-A.ARPA
	mss@Dartmouth.CSNet
	...decvax!dartvax!mss

!E!O!F!
#
#
echo extracting t/applback.text...
cat >t/applback.text <<'!E!O!F!'
$EXEC
R{un}bin-maccom
S{ettings}C{onvert Text}Y{es}
Y{es to filename searches}
F{inder}TEXT{type}
EDIT{Creator}
Y{es bundle bit}
N{o. don't always prompt}
Q{uit settings}
Lappl-cust.text
appl/cust.text
Lappl-tftp.text
appl/tftp.text
Lappl-telnet.text
appl/telnet.text
Lappl-custr.text
appl/custr.text
Lappl-tftpr.text
appl/tftpr.text
Lappl-telnetr.text
appl/telnetr.text
Lappl-cust.link.text
appl/cust.link.text
Lappl-tftp.link.text
appl/tftp.link.text
Lappl-telnet.link.text
appl/telnet.link.text
Lappl-custo.text
appl/custo.text
Lappl-tftpo.text
appl/tftpo.text
Lappl-telneto.text
appl/telneto.text
Lcopyright-notice.text
copyright/notice.text
Lt-assemble.text
t/assemble.text
Lt-compile.text
t/compile.text
Lt-depend.text
t/depend.text
Lt-link.text
t/link.text
Lt-make.text
t/make.text
Lt-netback.text
t/netback.text
Lt-applback.text
t/applback.text
Lsfmget-sfmgetfile.text
sfmget/sfmgetfile.text
Lsfmget-sfmgetr.text
sfmget/sfmgetr.text
Lsfmget-sfmtest.text
sfmget/sfmtest.text
Lsfmget-sfmtesto.text
sfmget/sfmtesto.text
Lsfmget-sfmtestr.text
sfmget/sfmtestr.text
Lsfmget-sfmget_asm.text
sfmget/sfmget_asm.text
Lsfmget-make.text
sfmget/make.text
E{ject}Q{uit}
$ENDEXEC

!E!O!F!
#
#
echo extracting t/assemble.text...
cat >t/assemble.text <<'!E!O!F!'
$EXEC
$if not (exists("-upper-%0.obj")) then
A{ssemble}%0  { input file: PROG.TEXT where PROG is the argument }
{no listing file}
%0 {output file PROG.OBJ}
$elseif newer("-upper-%0.text","-upper-%0.obj") then
A{ssemble}%0  { input file: PROG.TEXT where PROG is the argument }
{no listing file}
%0 {output file PROG.OBJ}
$endif
$DOIT
$ENDEXEC
!E!O!F!
#
#
echo extracting t/compile.text...
cat >t/compile.text <<'!E!O!F!'
$EXEC
P{ascal compiler}%0  { input file: PROG.TEXT where PROG is the argument }
{no listing file}
%0 {output file PROG.I}
$DOIT	  { forces compilation now so later dating will be right }
$ENDEXEC

!E!O!F!
#
#
echo extracting t/depend.text...
cat >t/depend.text <<'!E!O!F!'
*
*{ Please note the copyright notice in the file "copyright/notice" }
*
EXEC(progfile,dep1,dep2,dep3,dep4,dep5,dep6,dep7,dep8)

{ t-depend provides a make-like facility in which a Pascal ".text" file may
  be compiled or not depending on whether it is up to date with respect to
  ".obj" files it depends on.  It is recursive, and is meant to be submitted
  from another exec file, as shown:
  SUBMIT t-depend(progfile,dep1,dep2,...,dep8) }

IF dep1 = '' THEN { no dependencies given or left }
  IF NEWER("-upper-[progfile].text","-upper-[progfile].obj") THEN
    clear screen
    writeln "[progfile].text is newer than [progfile].obj"
    SUBMIT t-compile([progfile])
  ENDIF
ELSEIF NEWER("-upper-[dep1].obj","-upper-[progfile].obj") THEN
  clear screen
  writeln "[dep1].obj is newer than [progfile].obj"
  SUBMIT t-compile([progfile])
ELSE
  SUBMIT t-depend([progfile],[dep2],[dep3],[dep4],[dep5],[dep6],[dep7],[dep8])
ENDIF
ENDEXEC
!E!O!F!
#
#
echo extracting t/link.text...
cat >t/link.text <<'!E!O!F!'
$EXEC
{ USAGE: R<t-link(prog,creator) }
{ Modified to user RFB, resource file builder.  Each application has these files:
  %0.obj -- object file from Lisa Pascal compiler
  %0r.text -- instructions to resource compiler, makes %0r.rsrc
  %0o.text -- brief file to convert linked file to CODE resource file %0o.rsrc
  %0i.rsrc -- icons and other resource information built with Resource Editor
}
$IF NEWER("%0r.text","%0r.rsrc") THEN
r{un}bin-RMaker
%0r
$ENDIF
$IF NEWER("%0.obj","%0L.obj") THEN
$SUBMIT %0.link.text
R{un}bin-rmaker { convert linked file to CODE resource file }
%0o
$ENDIF
$
R{un}RFB       { Resource file builder }
%0.rsrc	  { output file }
%0r.rsrc  { compiled resource file }
*
$IF EXISTS("%0i.rsrc") THEN
%0i.rsrc  { icons, etc. }
*
$ENDIF
%0o.rsrc  { CODE resource file }
*
{ no more input files }
{ quit }
R{un}bin-MacCom { write the disk }
RYFYL%0.RSRC { strip prefix; set finder info; send PROG.RSRC via Lisa->Mac}
%0
APPL	 { set type to APPL }
%1	 { set creator to second arg }
Y{es, bundle bit}Q{uit}
$DOIT
$ENDEXEC

!E!O!F!
#
#
echo extracting t/make.text...
cat >t/make.text <<'!E!O!F!'
EXEC
submit t-assemble(net-task_asm)

submit t-assemble(net-call_asm)

submit t-assemble(net-ip_listen)

submit t-depend(net-calls,net-call_asm)

submit t-depend(net-err_lib)

submit t-depend(net-timer_lib)

submit t-depend(net-cust_lib)

submit t-depend(net-task_lib,net-err_lib,net-task_asm)

submit t-depend(net-ip_lib,net-task_lib,net-timer_lib,net-calls,net-err_lib)
submit t-depend(net-ip_lib,net-cust_lib)

submit t-depend(net-name_host,net-task_lib,net-timer_lib,net-ip_lib)

submit t-depend(net-icmp_lib,net-task_lib,net-timer_lib,net-ip_lib)
submit t-depend(net-icmp_lib,net-calls,net-err_lib)

submit t-depend(net-arp_lib,net-task_lib,net-ip_lib,net-timer_lib)

submit t-depend(net-udp_lib,net-task_lib,net-timer_lib)
submit t-depend(net-udp_lib,net-ip_lib,net-icmp_lib,net-calls,net-err_lib)

submit t-depend(net-name_user,net-task_lib,net-timer_lib)
submit t-depend(net-name_user,net-udp_lib,net-ip_lib,net-err_lib)

submit t-depend(net-tftp_defs,net-task_lib,net-timer_lib,net-err_lib)
submit t-depend(net-tftp_defs,net-ip_lib,net-udp_lib,net-calls)

submit t-depend(net-tftp_file,net-task_lib,net-timer_lib,net-err_lib)
submit t-depend(net-tftp_file,net-ip_lib,net-udp_lib,net-calls,net-tftp_defs)

submit t-depend(net-tftp_lib,net-task_lib,net-timer_lib,net-err_lib,net-ip_lib)
submit t-depend(net-tftp_lib,net-udp_lib,net-calls,net-tftp_defs,net-tftp_file)

submit t-depend(net-term_lib)

submit t-depend(net-tcp_lib,net-task_lib,net-timer_lib)
submit t-depend(net-tcp_lib,net-ip_lib,net-calls,net-err_lib,net-term_lib)

submit t-depend(net-tn_lib,net-task_lib,net-timer_lib,net-name_host,net-tftp_defs)
submit t-depend(net-tn_lib,net-ip_lib,net-tcp_lib,net-udp_lib,net-tftp_lib)
submit t-depend(net-tn_lib,net-name_user,net-err_lib,net-term_lib,net-calls)

submit t-depend(appl-cust,net-task_lib,net-ip_lib,net-name_user,net-cust_lib)

submit t-depend(appl-tftp,net-task_lib,net-timer_lib,net-name_host,net-arp_lib)
submit t-depend(appl-tftp,net-ip_listen,net-ip_lib,net-udp_lib,net-tftp_defs)
submit t-depend(appl-tftp,net-name_user,net-tftp_lib,net-err_lib,net-icmp_lib)

submit t-depend(appl-telnet,net-task_lib,net-timer_lib,net-err_lib,net-arp_lib)
submit t-depend(appl-telnet,net-ip_listen,net-ip_lib,net-udp_lib)
submit t-depend(appl-telnet,net-tcp_lib,net-tn_lib,net-term_lib,net-icmp_lib)

submit t-link(appl-cust,CUST)
submit t-link(appl-tftp,TFTP)
submit t-link(appl-telnet,TLNT)

$rbin-maccom
$EQ

ENDEXEC

!E!O!F!
#
#
echo extracting t/netback.text...
cat >t/netback.text <<'!E!O!F!'
$EXEC
R{un}bin-maccom
S{ettings}C{onvert Text}Y{es}
Y{es to filename searches}
F{inder}TEXT{type}
EDIT{Creator}
Y{es bundle bit}
N{o. don't always prompt}
Q{uit settings}
Lnet-calls.text
net/calls.text
Lnet-call_asm.text
net/call_asm.text
Lnet-err_lib.text
net/err_lib.text
Lnet-task_lib.text
net/task_lib.text
Lnet-task_asm.text
net/task_asm.text
Lnet-timer_lib.text
net/timer_lib.text
Lnet-cust_lib.text
net/cust_lib.text
Lnet-name_host.text
net/name_host.text
Lnet-arp_lib.text
net/arp_lib.text
Lnet-ip_listen.text
net/ip_listen.text
Lnet-icmp_lib.text
net/icmp_lib.text
Lnet-ip_lib.text
net/ip_lib.text
Lnet-udp_lib.text
net/udp_lib.text
Lnet-name_user.text
net/name_user.text
Lnet-tftp_defs.text
net/tftp_defs.text
Lnet-tftp_file.text
net/tftp_file.text
Lnet-tftp_lib.text
net/tftp_lib.text
Lnet-tcp_lib.text
net/tcp_lib.text
Lnet-term_lib.text
net/term_lib.text
Lnet-tn_lib.text
net/tn_lib.text
E{ject}Q{uit}
$ENDEXEC

!E!O!F!
#
#
echo extracting user...
cat >user <<'!E!O!F!'
MacIP User Manual
Tim Maroney
November 1985
-----------------

This paper describes the programs that have been written to allow Apple
Macintosh computers on an Appletalk network to communicate with computers on
the Internet.

In order to use any of these programs, your Macintosh must be connected to
an Appletalk network that is connected to a router of some sort.  One
example of a router is the Seagate router between Appletalk and Ethernet
that was developed at Stanford University.  Another is the "Butcher Board"
router developed at C-MU.  This is normally an issue for the system
maintenance staff, not a user; it is mentioned here so you won't think that
plugging an Appletalk connector into the back of your Mac magically allows
you to communicate with the Internet.

Three programs are available, CUSTOMIZE, TFTP, and TELNET.  CUSTOMIZE
manages a customization file that contains various information for use by
the other programs; it does not talk to Internet sites directly.  TFTP is a
simple file transfer program: it can be used to retrieve files to the
Macintosh from Internet sites, and vice versa.  TELNET is the Internet
terminal emulator; with TELNET, you can log on and conduct a session at any
Internet site on which you have an account.

In order to run any of these programs, double-click its icon, as is normal
on the Macintosh.

TFTP
----

The TFTP program uses the trivial file transfer protocol running on top of
the user datagram protocol to allow reliable file transfer between a
Macintosh and an Internet site.  The other Internet site may be a Macintosh
or another computer.

There are two possible modes in the TFTP program, server mode and user mode.
The TFTP program can be in one or the other mode, but not both.  Initially
it is in neither mode; the first selection you make from the "File" menu
sets the mode.

Server mode is rarely useful: when a Mac is acting as a server, other
Internet sites can initiate file transfers to and from it, but the Mac is
completely tied up while it is acting as a server.  To select server mode,
select "Server" from the "File" menu.  Once this is selected, the only
"File" menu commands available are "Abort" and "Quit".  You can still use
the commands from the "Show" menu to display information about the status of
the network.

User mode is the one usually used on the Mac.  To select user mode, pick
either "Get" or "Put" from the "File" menu.  This places you in user mode
and begins the file transfer process.  In any file transfer there are two
files, a local file (the file on the Macintosh) and a remote file (the file
on the other Internet site).  To get the local file name, the TFTP program
uses the Macintosh's standard file package, which will be familiar to you if
you have used other Macintosh applications.  To get the remote file name,
the TFTP program uses a special-purpose remote file dialog.  This contains
two fields which you can edit, one for the file's name and the other for the
name or address of the other Internet site.

Both the standard file dialogs and the remote file dialog have buttons
marked "OK" and "CANCEL".  Clicking in the "OK" button (or hitting the
"Return" key on the keyboard) tells the program to go on to the next step of
the file transfer.  Clicking "CANCEL" tells the program not to go ahead with
the file transfer and to return to plain user mode.

To get a file from another site to your Mac, use "Get".  To put a file from
your Macintosh to another site, use "Put".  If something is wrong with your
transfer, use "Abort" to terminate it.  When you are entirely finished with
TFTP, use "Quit" to leave the program and return to the Macintosh Finder.

The trivial file transfer protocol uses "transfer modes" to control file
transfer.  The transfer modes provide information about how the file is to
be transferred.  The Macintosh TFTP provides four transfer modes, which can
be selected from the remote file dialog, or from the "Settings" menu.  The
modes are "ASCII", "IMAGE", "OCTET", and "MACINTOSH".  The transfer mode
must be selected before you choose "Get" or "Put".  The initial transfer
mode is ASCII.

The first three transfer modes are almost identical, and are the only modes
that will be used with non-Macintosh computers in most cases.  To transfer a
document to or receive a document from a non-Mac computer, you should use
"ASCII" or "IMAGE" modes.  To transfer an application to or receive an
application from a non-Mac computer, you should use "OCTET" mode.  To
exchange a file with a Macintosh, use "MACINTOSH" mode.  Other computers
such as Vaxes and Suns will probably not support MACINTOSH mode transfers;
check with the system support staff for the computer in question.

A Macintosh file actually has two parts or "forks", the data fork and the
resource fork, unlike most other computers, on which files have only one
part.  This was a silly design decision in the Macintosh, since almost all
files have one of the forks empty.  It can also create problems in file
transfer between Macs and other machines.  In MACINTOSH transfer mode, both
forks (as well as a little bit of data known as the "finder information")
are sent, so there is no problem.  In ASCII and IMAGE modes, only the data
fork is sent: that will work for most documents such as MacWrite text files.
In OCTET mode, only the resource fork is sent: this suffices for most
Macintosh applications such as MacPaint or MacWrite.  In those rare cases
when both the resource and data forks are used, and you wish to send the
file to a system that does not support MACINTOSH mode file transfers, you
should first use one of the file compression programs such as BINHEX (not
supplied with the Macintosh Internet programs, but widely available) to
compress both forks into the data fork of another file, and then send the
BINHEXed file's data fork.

After receiving an application in OCTET mode, it will often be necessary
to run the "Set File" program, distributed separately, to set the bundle bit
and the application's creator.  Note: Over the long-haul networks such as
ARPANET and USENET, applications are usually transferred in "binhex" or some
other converted format, not in the binary form OCTET mode expects.  To get
an application in binhex format to your Mac via TFTP, use an ASCII mode
transfer and then run binhex once the transfer is complete.

The "Settings" menu contains a "Remote Directory" command.  Usually
Macintosh files are sent to a computer such as a UNIX (tm) machine, a
TOPS-20 system, or a VMS system, which has a notion of directories.  When
you "Put" a file, a remote directory prefix will be attached to the
beginning of the file name if you have specified a remote directory.  For
instance, to send all your files into the directory "/usr/you/macfiles" on a
UNIX machine, issue the "Remote Directory" command and specify
"/usr/you/macfiles/" (note the final slash).  To send all your files to the
directory "pk:<foo.bar>" on a TOPS-20 machine, issue "Remote Directory" and
specify "pk:<foo.bar>".  Remember that the directory is just a string which
is stuck onto the beginning of the remote file name; TFTP does not really
know anything about directories as such.

The file dialog that appears when you "Put" a file is a little different
from the standard one for the Macintosh.  It has a check box which allows
you to select more than one file from the list of files you are given.  All
files selected will be sent, one after another, until either all are sent or
an error happens.  (You can bypass the check box and use shift-clicking to
select multiple files if you are familiar and comfortable with
shift-clicking.)  This multiple-file capability is useful if you are using
TFTP to back up a number of files, usually program source code files, to
some other computer, because you do not have to issue a new "Put" command
for each file.  All the files will be sent to the same host, with the same
directory prefix and transfer mode.  The remote file dialog will appear
after you have specified multiple file names, allowing you to set the host,
the transfer mode, and the remote directory.

During file transfer in either user or server mode, some statistics about
the transfer will be displayed in a window that will appear on your screen.
If you want to get rid of the window, click anywhere in it and it will
vanish, reappearing when there is something else to say.  The same goes for
windows that appear as a result of selecting a command in the "Show" menu.

TELNET
------

With TELNET, you can log in to any computer on the Internet for which you
have an account.  The Macintosh TELNET is different from (and better than)
most other TELNET implementations in that you control the session with menu
commands instead of hard-to-remember and easy-to-screw-up control codes.
The TELNET terminal window emulates a DEC VT100 terminal, a popular type
that most host computers should know how to control.

To open a connection to another computer, use the "Open" command from the
"Commands" menu.  You will be asked for the name or address of the remote
computer.  To continue, click "OK"; to abort the attempt to connect, use
"CANCEL".  The TELNET program will then try to establish the connection.  If
it succeeds, the word "Open" will appear in the terminal window and the
remote computer will (in most cases) prompt you for your name and password.
When you are finished, logging out on the remote computer may automatically
close the connection, in which case "Closed" will appear on the screen.  If
this does not happen, then you can yourself close the connection by using
the "Close" command in the "Commands" menu.  You can also do this before you
log out, but you do not usually want to.

The keyboard sends what you would expect.  For instance, holding down the
clover key and "A" causes a "control-A" to be sent.  The chief exception is
the key in the upper left-hand corner, which sends an "ESCAPE" character. A
tilde ("~") can be sent by holding down "shift" and this key.  It is a known
bug in this release that there is no way to send a backquote, or any of the
control characters associated with the numerical keys, such as "control-^".

It is possible to use TFTP from inside TELNET.  Thus, you can log on to a
remote system, look around in its directories and so forth, and retrieve or
send files without having to leave TELNET.  This assumes that the remote
computer has a TFTP command.  The procedure is to issue a TFTP command on
the remote system that initiates a file transfer back to your Mac.  In order
to do this, you will have to know your Mac's Internet address or name, which
can be discovered using a command in the "Show" menu.  A TFTP server runs
alongside TELNET; the command from the remote system will cause that system
to try to connect to the server on your Mac.  If this succeeds, you will be
asked by your Mac to confirm or refuse the file transfer -- ordinarily you
will confirm it, since you sent the request in the first place, but it is
possible someone else could try to TFTP to or from your machine, in which
case you would probably want to deny the request and report the incident to
the local security staff.  While a file transfer is going on, it is possible
but unwise to type characters to the remote system.  You will be notified
when the transfer is complete.  You can deny all TFTP requests from the
"Commands" menu; "TFTP Service" is checked if TFTP requests will be serviced
as described above.  TFTP service inside TELNET can only be used on a
Macintosh which has 512K or more memory.  It may be used inside Switcher;
TELNET is pre-configured for Switcher to allow enough memory for TFTP.

It is possible to listen for incoming TELNET requests by using the "Listen"
command.  This is usually not useful; it was added for testing.  It could be
used to chat back and forth between two users on different machines: one
user would put his Mac in listening state, the other would TELNET to his
machine.  The two could then type back and forth to each other's terminal
windows.

It is possible to specify that what you type should not be sent until you
type a return, by selecting "Send Immediately" from the "Commands" menu.
This command will be checked if TELNET is in the normal state, in which
characters are sent as soon as you type them, and not checked if characters
will be held until return is typed.  This can be useful because it reduces
the network overhead of the session.  However, it is impossible to see what
you've typed until you hit the return, unless TELNET has been given the
"Local Echo" command.  It is possible to cause data to be sent before typing
a return by using the "Expedite Data" command from the "Send" menu.

Most computers operate in remote echo mode: characters you type are not
directly printed on the screen; they are sent to the remote computer, which
then echoes the characters back for printing on your terminal.  Computers
which operate in local echo mode can be communicated with by selecting
"Local Echo" from the "Send" menu after the connection is open.

The "Are You There?" command from the "Send" menu checks to see that the
remote computer is still connected.  If no response is received within a few
seconds, TELNET will assume that the remote computer has crashed or
otherwise disconnected, and shut down your side of the connection.

"Abort Output" and "Break" from the "Send" menu cause special TELNET
information to be transmitted.  The remote computer may or may not handle
these commands.  In general, "Abort Output" will keep text that's being sent
from being sent, perhaps while a file is being printed on your screen, but
the program on the remote computer will not be interrupted.  "Break" will do
whatever is appropriate on the system you are connected to, usually
interrupting the program on the remote computer.

As with TFTP, there is a "Show" menu from which various information can be
gotten.  Any windows created when you select a command from the "Show" menu
can be gotten rid of by clicking anywhere in them.

Some menu commands in the "Show" menu are permanently disabled; they were
removed to allow TELNET to run on a 128K Macintosh or in a small Switcher
memory chunk.  You should be able to get along just fine without them.

In the future, TELNET will save text that scrolls up past the top of the
screen, will allow TELNET traffic to be saved into a file, will allow you to
send a text file as if you were typing it in, and will allow characters to
be printed to the screen at a much higher rate.  These MacTerminal-like
capabilities are not included in the November 1985 release of TELNET.
!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting appl/cust.link.text...
cat >appl/cust.link.text <<'!E!O!F!'
$EXEC
L{ink}?
+X
{ no more options}
appl-cust
obj-quickdraw
obj-tooltraps
obj-ostraps
obj-prlink
obj-packtraps
obj-rtlib
obj-pasinit
obj-paslibasm
obj-paslib
obj-abpascalls
net-cust_lib
net-ip_lib
net-name_user
{no more object files}
{list on console}
appl-custL.OBJ
$ENDEXEC
!E!O!F!
#
#
echo extracting appl/cust.text...
cat >appl/cust.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
PROGRAM Customize;

{ Please note the copyright notice in the file "copyright/notice" }

{$L-}

   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-PackIntf	  } PackIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-cust_Lib	  } Cust_Lib,
      {$U net-ip_lib	  } IP_Lib,
      {$U net-name_user	  } Name_User;

{$L+}

VAR
      screen,dragRect: Rect;
      myEvent: EventRecord;
      whichWindow: WindowPtr;
      theItem: INTEGER;

      MyDialog: DialogPtr;
      dRecord: DialogRecord;
      TheItemType: Integer;
      TheItemBox: Rect;

   PROCEDURE SetupValue(Val:LongInt;Where:Integer);
   { Where is the item number into which to write the data }
   VAR LocalItemHandle: Handle;
       TempText: STR255;
   BEGIN
      GetDItem(MyDialog,Where,TheItemType,LocalItemHandle,TheItemBox);
      cvt_inaddr(Val,TempText);
      SetIText(LocalItemHandle,TempText);
   END;

   FUNCTION GetValue(Where:Integer): LongInt;
   { Read a value from a field }
   VAR LocalItemHandle: Handle;
       TempText: STR255;
   BEGIN
       GetDItem(MyDialog,Where,TheItemType,LocalItemHandle,TheItemBox);
       GetIText(LocalItemHandle,TempText);
       GetValue := convert_name(TempText);
   END;

   PROCEDURE DoCustom;
   CONST
	OKBut = 1;
	CancelBut = 2;
	LocalIPField = 3;
	GWIPField = 4;
	NSIPField = 5;
	UserField = 6;
	DefHField = 7;
	ABRadio = 8;
	LocIPHeadr = 9;
	GWIPHeader = 10;
	NSIPHeader = 11;
	UserHeader = 12;
	DefHHeader = 13;
	ErrField = 14;

	CustDialog = 1;


   TYPE
	TwoHandle = RECORD CASE Integer OF
			0:( h: Handle);
			1:( ch: ControlHandle);
			END;
   VAR
	i: Integer;
	ItemHit: Integer;
	LocalItemHandle: Handle;
	ErrHandle, ABButHandle: TwoHandle;
	FRN: Integer; { File reference number for the custimzation values }
	CurVal:	     CustRecord;
	RLength: LongInt;
	OSStatus: OSErr;
	ErrMsg: STR255;
	TLIP,TGWIP,{ TTSIP,} TNSIP: in_name;
	TUser,THost: STR255;
	myNet,myNode:INTEGER;

   BEGIN

     { Some values in case something goes wrong }

     if GetNodeAddress(myNode,myNet) <> noErr then myNode := 0;
     CurVal.LocalIPAddr:= $80020000 + myNode;
     CurVal.GateWIPAddr:= $80020040;
     CurVal.NameServer := $80020040;
     CurVal.TimeServer := $80020040;
     CurVal.UserName := 'AppleMAC';
     CurVal.DefHost := 'Unknown';
     CurVal.UseAB := TRUE;

     { Try to read in old values }
     OSStatus := FSOpen(CFileName, {Current Vol} 0, FRN);
     IF OSStatus = noErr THEN BEGIN
	{ File is there, read in the values }
	RLength := sizeof(CustRecord);
	{ Get to the start of the file }
	OSStatus := SetFPos(FRN,fsFromStart,0);
	IF OSStatus = noErr
	    THEN OSStatus := FSRead(FRN,RLength,@CurVal);
	ErrMsg := 'Current values from customization file are shown.';
	END
     ELSE IF OSStatus = fnfErr THEN BEGIN
	{ File wasn't found, so create it and fill with default values }
	{ Not really a text file, but this allows other programs to try
	  to diddle with it }
	OSStatus := Create(CFileName, { Vol } 0, { Creator } '????',
						 { FileType} 'TEXT');
	{ After creating it, open it and make sure we can get the room }
	IF OSStatus = noErr
	     THEN OSStatus := FSOpen(CFileName, {Current Vol} 0, FRN);
	{ and get the room }
	RLength := sizeof(CustRecord);
	IF OSStatus = noErr
	    THEN OSStatus := Allocate(FRN,RLength);

	ErrMsg := 'Customization file not found - creating it with default values.';
	END;

     IF OSStatus <> noErr THEN BEGIN
	{ Have problem, may be cannot correct them }
	NumToString(OSStatus,ErrMsg);
	ErrMsg := concat('Could not open customization file - status ',ErrMsg);
	END;


     MyDialog :=  GetNewDialog(CustDialog,@dRecord,POINTER(-1));
     GetDItem(MyDialog,ABRadio,TheItemType,ABButHandle.h,TheItemBox);
     GetDItem(MyDialog,ErrField,TheItemType,ErrHandle.h,TheItemBox);

     REPEAT

     { Now fill in the values read from the file }
     SetCtlValue(ABButHandle.ch,ORD(CurVal.UseAB));
     SetupValue(CurVal.LocalIPAddr,LocalIPField);
     SetupValue(CurVal.GateWIPAddr,GWIPField);
     { SetupValue(CurVal.TimeServer,TSIPField); }
     SetupValue(CurVal.NameServer,NSIPField);
     GetDItem(MyDialog,UserField,TheItemType,LocalItemHandle,TheItemBox);
     SetIText(LocalItemHandle,CurVal.UserName);
     GetDItem(MyDialog,DefHField,TheItemType,LocalItemHandle,TheItemBox);
     SetIText(LocalItemHandle,CurVal.DefHost);
     SetIText(ErrHandle.h,ErrMsg);

     ModalDialog(NIL,ItemHit);
     { Read in the fields }

     ErrMsg := '';

     TLIP := GetValue(LocalIPField);
     IF TLIP = 0
	 THEN ErrMsg := 'Local IP Address is ill formed.'
	 ELSE CurVal.LocalIPAddr := TLIP;
     IF CurVal.UseAB AND (ErrMsg = '') THEN BEGIN { Now use AB value }
	 CurVal.LocalIPAddr := BitAnd(CurVal.LocalIPAddr,$FFFFFF00) + myNode;
	 SetupValue(CurVal.LocalIPAddr,LocalIPField);
	 END;

     TGWIP := GetValue(GWIPField);
     IF TGWIP = 0
	 THEN ErrMsg := 'Gateway IP Address is ill formed.'
	 ELSE CurVal.GateWIPAddr := TGWIP;
     { TTSIP := GetValue(TSIPField);
     IF TTSIP = 0
	 THEN ErrMsg := 'Timer Server IP Address is ill formed.'
	 ELSE CurVal.TimeServer := TTSIP; }
     TNSIP := GetValue(NSIPField);
     IF TNSIP = 0
	 THEN ErrMsg := 'Name Server IP Address is ill formed.'
	 ELSE CurVal.NameServer := TNSIP;
     GetDItem(MyDialog,UserField,TheItemType,LocalItemHandle,TheItemBox);
     GetIText(LocalItemHandle,TUser);
     IF length(TUser)>8
	 THEN ErrMsg := 'User name greater than 8 character'
     ELSE IF length(TUser) = 0
	 THEN ErrMsg := 'Null user name is not permitted.'
     ELSE CurVal.UserName := TUser;
     GetDItem(MyDialog,DefHField,TheItemType,LocalItemHandle,TheItemBox);
     GetIText(LocalItemHandle,THost);
     IF length(THost) = 0
	 THEN ErrMsg := 'Null host name is not permitted.'
     ELSE CurVal.DefHost := THost;

       CASE ItemHit OF
	  OKBut: BEGIN

		 IF ErrMsg = '' THEN BEGIN { All values OK }
		     { Writeout the file }
		     ErrMsg := 'Please confirm by clicking ''OK''';
		     SetIText(ErrHandle.h,ErrMsg);

		     ModalDialog(NIL,ItemHit);
		     IF ItemHit <> OKBut THEN BEGIN
			 ItemHit := ErrField;
			 ErrMsg := 'Save has been canceled.';
			 SetIText(ErrHandle.h,ErrMsg);
			 END
		     ELSE BEGIN
		     RLength := sizeof(CustRecord);
		     OSStatus := SetFPos(FRN,fsFromStart,0);
		     IF OSStatus = noErr THEN OSStatus := FSWrite(FRN,RLength,@CurVal);
		     IF OSStatus <> noErr THEN BEGIN
			 ErrMsg := 'Trouble writing the customization file.';
			 SetIText(ErrHandle.h,ErrMsg);
			 ModalDialog(NIL,ItemHit);
			 END;
		     IF OSStatus = noErr THEN OSStatus := FSClose(FRN);
		     IF OSStatus <> noErr THEN BEGIN
			 ErrMsg := 'Trouble closing the customization file.';
			 SetIText(ErrHandle.h,ErrMsg);
			 ModalDialog(NIL,ItemHit);
			 END;
		     IF OSStatus <> noErr THEN ItemHit := ErrField;
		     END END
		     ELSE ItemHit := ErrField;
		 END;

	  CancelBut: BEGIN
		 { Person decided to punt }
		 ErrMsg := 'Are you sure you want to cancel? (Select OK)';
		 SetIText(ErrHandle.h,ErrMsg);
		 ModalDialog(NIL,ItemHit);
		 IF ItemHit <> OKBut THEN BEGIN
		    ItemHit := ErrField;
		    ErrMsg := 'Cancel ''canceled''';
		    END
		    ELSE OSStatus := FSClose(FRN);
		 END;

	  ABRadio: BEGIN
		 { Person wants some calculation based on the AB address to
		   be used for Local IP Address }
		 CurVal.UseAB := NOT CurVal.UseAB;
		 SetCtlvalue(ABButHandle.ch,ORD(CurVal.UseAB));
		 IF CurVal.UseAB THEN BEGIN { Now use AB value }
		    ErrMsg := 'Local IP Address now a function of Applebus node number.';
		    CurVal.LocalIPAddr := BitAnd(CurVal.LocalIPAddr,$FFFFFF00) +
					  myNode;
		    END
		    ELSE BEGIN
			 ErrMsg := 'Local IP Address is no longer a function of the Applebus node number.';
		    END;
		 END;

       END;
     UNTIL ItemHit in [OKBut, CancelBut];

     CloseDialog(MyDialog);
   END;


   BEGIN { main program }
      InitGraf(@thePort);
      InitFonts;
      FlushEvents(everyEvent,0);
      InitWindows;
      TEInit;
      InitDialogs(NIL);
      InitCursor;

      screen := screenBits.bounds;
      SetRect(dragRect,4,24,screen.right-4,screen.bottom-4);

      DoCustom;

   END.
!E!O!F!
#
#
echo extracting appl/custo.text...
cat >appl/custo.text <<'!E!O!F!'
appl-custo.rsrc

Type CODE
  appl-custL,0
!E!O!F!
#
#
echo extracting appl/custr.text...
cat >appl/custr.text <<'!E!O!F!'
*  appl-custr -- Resource input for customizer
*
*{ Please note the copyright notice in the file "copyright/notice" }

appl-custr.Rsrc

Type MENU
  ,256
  File
    Customize
    User
    Quit

Type DLOG
 ,1
 30 20 300 490
 Visible 1 NoGoAway 0
 3

Type DITL
  ,3
  14
  BtnItem Enabled
    20 110  35 190
OK

  BtnItem Enabled
    20 260 35 340
Cancel

  EditText Disabled
     50 195 65 350
DefaultLocalIPAddress

  EditText Disabled
     75 195 90 350
DefaultGWIPAddress

  EditText Disabled
     100 195 115 350
DefaultNSIPAddress

  EditText Disabled
     125 195 140 350
DefaultUserName

  EditText Disabled
     150 195 165 350
DefaultHostName

  ChkItem Enabled
    175 10 190 350
Base Local IP Address on Applebus Node Number

  StatText Disabled
     50 10 65 190
Local IP Address:

  StatText Disabled
     75 10 90 190
Gateway IP Address:

  StatText Disabled
     100 10 115 190
Name Server IP Address:

  StatText Disabled
     125 10 140 190
User Name:

  StatText Disabled
     150  10 165 190
Default Foreign Host:

  StatText Disabled
     200 10 260 400
Customization file not found -- creating it.

Type BNDL
  ,128
  CUST 0
  2
  ICN# 1
  0 128
  FREF 1
  0 128

Type FREF
  ,128
  APPL 0

Type CUST = STR
  ,0
CUST Version 2, 10 September 1985

!E!O!F!
#
#
echo extracting appl/telnet.link.te...
cat >appl/telnet.link.te <<'!E!O!F!'
$EXEC
L{ink}?
+X
{ no more options}
appl-telnet
obj-quickdraw
obj-tooltraps
obj-ostraps
obj-prlink
obj-packtraps
obj-rtlib
obj-pasinit
obj-paslibasm
obj-paslib
obj-abpascalls
net-err_lib
net-task_lib
net-task_asm
net-timer_lib
net-cust_lib
net-name_host
net-arp_lib
net-ip_listen
net-icmp_lib
net-ip_lib
net-udp_lib
net-name_user
net-tftp_defs
net-tftp_file
net-tftp_lib
net-tcp_lib
net-term_lib
net-tn_lib
net-calls
net-call_asm
{no more object files}
{list on console}
appl-telnetL.OBJ
$ENDEXEC

!E!O!F!
#
#
echo extracting appl/telnet.text...
cat >appl/telnet.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
{$DECL LISTEN}
{$SETC LISTEN := true}
{$DECL TCPSTATS}
{$SETC TCPSTATS := false}
{$DECL UDPSTATS}
{$SETC UDPSTATS := false}
{$DECL IPSTATS}
{$SETC IPSTATS := false}
PROGRAM TELNET;

{ Please note the copyright notice in the file "copyright/notice" }

{$L-}
   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-PackIntf	  } PackIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-err_lib	  } Err_Lib,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-ip_lib	  } IP_Lib,
      {$U net-arp_lib	  } ARP_Lib,
      {$U net-icmp_lib	  } ICMP_Lib,
      {$U net-UDP_Lib	  } UDP_Lib,
      {$U net-TCP_Lib	  } TCP_Lib,
      {$U net-TFTP_Lib	  } TFTP_Lib,
      {$U net-tn_lib	  } TN_Lib,
      {$U net-term_lib	  } Term_Lib;

{$L+}

   CONST
      lastMenu = 4;
      appleMenu = 1; { menu ID for desk accessory menu }
      CmdMenu = 257;  { menu ID for Command menu }
      SndMenu = 256; { menu ID for Send menu }
      ShowMenu = 258; { menu ID for Show menu }

   VAR
      myMenus: ARRAY [1..lastMenu] OF MenuHandle;
      myEvent: EventRecord;
      code,refNum: INTEGER;
      whichWindow: WindowPtr;
      theMenu,theItem: INTEGER;
      dragRect,screen:Rect;
      mainstack: integer;
      TempText:STR255;

{$S InitSeg}

   PROCEDURE SetUpMenus;
   { Once-only initialization for menus }

      VAR
	 i: INTEGER;
	 appleTitle: STRING[1];

      BEGIN
	 InitMenus; { initialize Menu Manager }
	 appleTitle := ' '; appleTitle[1] := CHR(20);
	 myMenus[1] := NewMenu(appleMenu,appleTitle);
	 AddResMenu(myMenus[1],'DRVR'); { desk accessories }
	 myMenus[2] := GetMenu(CmdMenu);
	 myMenus[3] := GetMenu(SndMenu);
	 myMenus[4] := GetMenu(ShowMenu);

	 FOR i := 1 TO lastMenu DO InsertMenu(myMenus[i],0);
	 DrawMenuBar;
	 if NOT tn_tftp then DisableItem(myMenus[1],0); {disable desk accessories}
{$IFC LISTEN}
	EnableItem(myMenus[2],5);
{$ENDC}
{$IFC TCPSTATS}
	EnableItem(myMenus[4],1);
{$ENDC}
{$IFC UDPSTATS}
	EnableItem(myMenus[4],4);
{$ENDC}
{$IFC IPSTATS}
	EnableItem(myMenus[4],5);
{$ENDC}
      END; { of SetUpMenus }

{$S	    }

   PROCEDURE DoCommand(mResult: LongInt);

      VAR
	 DummyInt,i: Integer;
	 TempLong:LongInt;
	 myNode,myNet:INTEGER;

      BEGIN
	 theMenu := HiWord(mResult);
	 theItem := LoWord(mResult);
	 CASE theMenu OF

	    appleMenu:
	       BEGIN
	       GetItem(myMenus[1],theItem,TempText);
	       refNum := OpenDeskAcc(TempText);
	       END;

	    CmdMenu:
		TelnetMenuCmd(theItem,myMenus[2]);

	    SndMenu:
		SendMenuCmd(theItem,myMenus[3]);

	    ShowMenu:
	       begin
	       case theItem of
{$IFC TCPSTATS}
		   1:  showstats;
{$ENDC}

		   2:  { Address of my machine }
		       begin
		       cvt_inaddr(in_mymach(0),Msg);
		       Message(StrCvt('My Internet address:'),@Msg);
		       end;

		   3: { Show Local Appletalk Address }
		      if GetNodeAddress(myNode,myNet) = noErr then
			begin
			Msg := 'Network: ';
			NumToStr(myNet,TempText);
			insert(TempText,Msg,length(Msg)+1);
			insert(', Node: ',Msg,length(Msg)+1);
			NumToStr(myNode,TempText);
			insert(TempText,Msg,length(Msg)+1);
			Message(StrCvt('My Appletalk address:'),@Msg);
			end;

{$IFC UDPSTATS}
	       4:       udp_table;
{$ENDC}
{$IFC IPSTATS}
	       5:       in_stats;
{$ENDC}
	       end;
	       end;

	 END; { of menu case }
	 HiliteMenu(0);

      END; { of DoCommand }

{$S InitSeg }

FUNCTION StackBase:LongInt;
CONST CurStackBase = $908;      { system global }
TYPE LongPtr = ^LongInt;
BEGIN
	StackBase := LongPtr(CurStackBase)^;
END;

PROCEDURE InitNetwork;
BEGIN
	MainStack := 4096;
	IPStack := 4096;
	SetApplLimit(POINTER(StackBase -
	      (MainStack + IPStack + ARPTKSZ + TFTKSZ + TCPTKSZ + TNTKSZ + 2048)));
	MaxApplZone;
	MoreMasters; MoreMasters; MoreMasters; MoreMasters; MoreMasters;
	if (ORD4(ApplicZone^.bkLim) - ORD4(ApplicZone)) < $19000 { 100K }
	then tn_tftp := false
	else tn_tftp := true;

	InitGraf(@thePort);
	InitFonts;
	FlushEvents(everyEvent,0);
	InitWindows;
	SetUpMenus;
	InitDialogs(NIL);
	InitCursor;
	TEInit;

	screen := screenBits.bounds;
	SetRect(dragRect,4,24,screen.right-4,screen.bottom-4);

	ErrInit;
	if MPPOpen <> noErr then
		Fatal(StrCvt('Can''t open Mac Protocol Package'),false);
	Main_Task := tk_init(mainstack);
	tm_init;
	in_init;
	IcmpInit;
	GgpInit;
	UdpInit;
	if tn_tftp then tftpinit;
	tcp_init(512);
	tel_init;

	tn_done := FALSE;
END;

{$S	  }

PROCEDURE MainEventLoop;
   CONST NetworkEvt = 10;
   VAR
	dp:DialogPtr;
	itemHit:INTEGER;
	i:INTEGER;
BEGIN

   REPEAT
      SystemTask;

      if GetNextEvent(everyEvent,myEvent) then begin

	 if IsDialogEvent(myEvent) then
		begin
		if NOT DialogSelect(myEvent,dp,itemHit) then
			begin
			if (myEvent.what = mouseDown) then
				begin
				IF NOT ClickResolve(myEvent) THEN ;
				end
			else if (myEvent.what = activateEvt) then
				begin
				if NOT ActResolve(myEvent) then ;
				end;
			cycle;
			end;
		end;

	 CASE myEvent.what OF

	    mouseDown:
	       BEGIN
	       code := FindWindow(myEvent.where,whichWindow);
	       CASE code OF

		  inMenuBar:
			begin
			TNFixMenu(myMenus[2]);
			FixSendMenu(myMenus[3]);
			DoCommand(MenuSelect(myEvent.where));
			end;

		  inSysWindow: SystemClick(myEvent,whichWindow);

		  inDrag: DragWindow(whichWindow,myEvent.where,dragRect);

		  inGrow,inContent:
		     BEGIN
		     IF whichWindow = myWindow then
			begin
			IoClick(code,myEvent.where,myEvent.modifiers)
			end
		     ELSE IF whichWindow <> FrontWindow THEN
			SelectWindow(whichWindow)
		     END;

		   inGoAway: ;
	       END; { of code case }
	       END; { of mouseDown }

	    keyDown,autoKey:
	       IF (myWindow=FrontWindow) THEN
		   begin
		   { when connection is open, keyboard input gets treated as
		     terminal input; at other times it is ignored }
		   em_input(@myEvent,TempText);
		   for i := 1 to length(TempText) do
			gt_usr(TempText[i]);
		   end;

	    activateEvt:
		  if POINTER(myEvent.message) = myWindow then
			IoActivate(myEvent.modifiers);

	    updateEvt:
		  if POINTER(myEvent.message) = myWindow then
			IoUpdate;
	 END; { of event case }
      END { of if GetNextEvent }

   ELSE IF myEvent.what = nullEvent THEN begin
	    tk_yield; { See if anyone else wants to execute }
	    IOIdle;
	    end;

   UNTIL tn_done;
END; { end of My Main Event Loop }

BEGIN { main program }
      InitNetwork;
      UnloadSeg(@InitNetwork);
      MainEventLoop;
      in_close;
      tm_allFree;
END.
!E!O!F!
#
#
echo extracting appl/telneto.text...
cat >appl/telneto.text <<'!E!O!F!'
appl-telneto.rsrc

Type CODE
  appl-telnetL,0
!E!O!F!
#
#
echo extracting appl/telnetr.text...
cat >appl/telnetr.text <<'!E!O!F!'
* Resource compiler file for TELNET
*
*{ Please note the copyright notice in the file "copyright/notice" }
*

appl-telnetr.Rsrc

Type MENU
  ,256
  Send
Local Echo
"Are You There?"
"Abort Output"
"Break"
Expedite Data

  ,257
  Commands
Open...
Close
Send Immediately
TFTP Service
(Listen
Quit

  ,258
  Show
(TCP Statistics
My Internet Address
My Appletalk Address
(Active UDP Connections
(IP Statistics

Type DLOG
  ,11
  90 50 200 440
  Visible 1 NoGoAway 0
  13
Confirm File Transfer

Type DITL
  ,13
  4
  BtnItem Enabled
    80 90 105 150
YES

  BtnItem Enabled
    80 240 105 300
NO

  StatText Disabled
     5 20 25 370
You have received a TFTP (file transfer) request.

  StatText Disabled
     25 20 75 370
The host ^0 wants to ^1 the file "^2".  Is that all right?

Type DLOG
  ,49
  100 125 245 375
  Invisible 3 NoGoAway 0
  51
Error Box

Type DITL
  ,51
  2
  StatText Disabled
    10 10 25 240
A Network Error Happened!

  StatText Disabled
    80 10 135 240
Set by SetIText

Type DLOG
 ,31
 100 125 245 375
 Visible 1 NoGoAway 0
 62
Name Foreign Host

Type DITL
  ,62
  4
  BtnItem Enabled
    10 50 40 110
OK

  BtnItem Enabled
    10 140 40 200
CANCEL

  EditText Disabled
     115 10 130 240
DefaultForeignHost

  StatText Disabled
     50 10 100 240
Please type in the name or number of the foreign host you want to reach.

Type ALRT
  ,333
  80 100 255 420
  444
  FFFF

Type DITL
  ,444
  3
  BtnItem Enabled
    140 110 165 210
QUIT

  StatText Disabled
    10 90 55 300
A fatal error happened!	 The program will die.

  StatText Disabled
    80 20 135 300
^0

Type ALRT
  ,666
  80 100 255 420
  777
  FFFF

Type DITL
  ,777
  5
  BtnItem Enabled
    140 110 165 210
HALT

  StatText Disabled
    10 90 55 300
Fatal Error! A task overflowed its stack!

  StatText Disabled
    80 20 95 300
Stack Pointer about ^0

  StatText Disabled
    100 20 115 300
Task control block at ^1

  StatText Disabled
    120 20 135 300
Task name is ^2

Type DLOG
  ,77
  50 100 295 375
  Visible 4 NoGoAway 0
  78
IP Statistics

Type DITL
  ,78
  22
  StatText Disabled
    15 180 30 265
precv

  StatText Disabled
    35 180 50 265
psnt

  StatText Disabled
    55 180 70 265
pdrop

  StatText Disabled
    75 180 90 265
bdchk

  StatText Disabled
    95 180 110 265
unprot

  StatText Disabled
    115 180 130 265
bdvers

  StatText Disabled
    135 180 150 265
bdlen

  StatText Disabled
    155 180 170 265
ttlexp

  StatText Disabled
    175 180 190 265
frags

  StatText Disabled
    195 180 210 265
free

  StatText Disabled
    215 180 230 265
fail

  StatText Disabled
    15 10 30 170
Packets Received:

  StatText Disabled
    35 10 50 170
Packets Sent:

  StatText Disabled
    55 10 70 170
Packets Dropped:

  StatText Disabled
    75 10 90 170
Bad Checksums:

  StatText Disabled
    95 10 110 170
Unhandled Protocols:

  StatText Disabled
    115 10 130 170
Bad Versions:

  StatText Disabled
    135 10 150 170
Bad Lengths:

  StatText Disabled
    155 10 170 170
TTL Expired:

  StatText Disabled
    175 10 190 170
Fragments:

  StatText Disabled
    195 10 210 170
Free Packets:

  StatText Disabled
    215 10 230 170
Listen Failures:

Type DLOG
  ,93
  100 75 250 425
  Visible 4 NoGoAway 0
  94
UDP Statistics

Type DITL
  ,94
  15
  StatText Disabled
    10 10 25 70
Local

  StatText Disabled
    10 78 25 138
Foreign

  StatText Disabled
    10 146 25 206
Host

  StatText Disabled
    10 214 25 276
Handler

  StatText Disabled
    10 284 25 344
Cn

  StatText Disabled
    30 10 45 70


  StatText Disabled
    30 78 45 138


  StatText Disabled
    30 146 45 206


  StatText Disabled
    30 214 45 276


  StatText Disabled
    30 284 45 344


  StatText Disabled
    50 10 65 70


  StatText Disabled
    50 78 65 138


  StatText Disabled
    50 146 65 206


  StatText Disabled
    50 214 65 276


  StatText Disabled
    50 284 65 344


Type BNDL
  ,128
  TLNT 0
  2
  ICN# 1
  0 128
  FREF 1
  0 128

Type FREF
  ,128
  APPL 0

*Switcher application size resource
Type SIZE = HEXA
  ,-1
  00000002000000018000

Type TLNT = STR
  ,0
TELNET Version 1.2, 10 September 1985

!E!O!F!
#
#
echo extracting appl/tftp.link.text...
cat >appl/tftp.link.text <<'!E!O!F!'
$EXEC
L{ink}?
+X
{ no more options}
appl-tftp
obj-quickdraw
obj-tooltraps
obj-ostraps
obj-prlink
obj-packtraps
obj-rtlib
obj-pasinit
obj-paslibasm
obj-paslib
obj-abpascalls
sfmget-sfmintf
sfmget-sfmintf_asm
net-err_lib
net-task_lib
net-task_asm
net-timer_lib
net-cust_lib
net-name_host
net-arp_lib
net-ip_listen
net-icmp_lib
net-ip_lib
net-udp_lib
net-name_user
net-tftp_defs
net-tftp_file
net-tftp_lib
net-calls
net-call_asm
{no more object files}
{list on console}
appl-tftpL.OBJ
$ENDEXEC

!E!O!F!
#
#
echo extracting appl/tftp.text...
cat >appl/tftp.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
PROGRAM TFTP;

{ Please note the copyright notice in the file "copyright/notice" }

  { TFTP - Trivial File transfer over Applebus		        }
  {	    by Mark Sherman (Dartmouth) and Tim Maroney (C-MU)  }

{$L-}
   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-PackIntf	  } PackIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-err_lib	  } Err_Lib,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-ip_lib	  } IP_Lib,
      {$U net-arp_lib	  } ARP_Lib,
      {$U net-icmp_lib	  } ICMP_Lib,
      {$U net-UDP_Lib	  } UDP_Lib,
      {$U net-name_user	  } Name_User,
      {$U net-tftp_defs	  } TFTP_Defs,
      {$U net-tftp_lib	  } TFTP_Lib,
      {$U net-name_host	  } NameHost,
      {$U sfmget-SFMIntf  } SFMIntf;

{$L+}

   CONST
      lastMenu = 4; {number of menus }

      appleMenu = 1; { menu ID for desk accessory menu }
      CmdMenu = 256;  { menu ID for Command menu }
      ShowMenu = 258; { menu ID for Show menu }
      SettingMenu = 257; { menu ID for Setting menu }

      RFileDialog = 11;

      MCmd = 2;
      MSetting = 3;
      MShow = 4;
      ServerItem = 3;

      MYTKSZ = 6144;

   VAR
      myMenus: ARRAY [1..lastMenu] OF MenuHandle;
      SGOK,SPOK:Boolean;
      doneFlag: BOOLEAN;
      myEvent: EventRecord;
      code,refNum: INTEGER;
      whichWindow: WindowPtr;
      theMenu,theItem: INTEGER;
      dragRect,screen:Rect;
      TempText:STR255;
      ServerEnabled: Boolean;
      INFOMSGChked, NETERRChked, PROTERRChked : BOOLEAN;
      LocalFileName, DestFileName, DestHostName, Directory: STR255;
      LocalVolume:Integer;
      tftpnum: Integer;
      fhost: in_name;
      ModeToUse: Integer;
      IPuninited: Boolean;
      UserTask, Server_Task: Ref_Task;
      UserDirection: Integer;
      UserRunning: Boolean;
      mainstack: integer;

{$S TFTPSeg }

PROCEDURE tfshow(host:in_name;fileName:StringPtr;mode:Integer);
BEGIN
	  Msg := 'tftp #';
	  NumToStr(tftpnum,TempText);
	  insert(TempText,Msg,length(Msg)+1);
	  tftpnum := tftpnum + 1;
	  insert(', with ',Msg,length(Msg)+1);
	  cvt_inaddr(host,TempText);
	  insert(TempText,Msg,length(Msg)+1);
	  insert(': ',Msg,length(Msg)+1);
	  IF mode = GET THEN insert('Receiving ',Msg,length(Msg)+1)
	  ELSE IF mode = PUT THEN insert('Sending ',Msg,length(Msg)+1)
	  ELSE insert(' BAD direction ',Msg,length(Msg)+1);
	  insert(fileName^,Msg,length(Msg)+1);
	  Message(StrCvt('Starting file transfer.'),@Msg);
END;

FUNCTION tfprint(host:in_name;fileName:StringPtr;mode:Integer): Integer;
BEGIN
     if ((mode = PUT) and NOT SGOK) or ((mode = GET) and NOT SPOK) THEN BEGIN
	   Msg := 'From ';
	   cvt_inaddr(host,TempText);
	   insert(TempText,Msg,length(Msg)+1);
	   insert(': Tried to ',Msg,length(Msg)+1);
	   IF mode = GET THEN insert('put ',Msg,length(Msg)+1)
	   ELSE IF mode = PUT THEN insert('get ',Msg,length(Msg)+1);
	   insert(fileName^,Msg,length(Msg)+1);
	   Message(StrCvt('Rejected transfer request.'),@Msg);
	   tfprint := 0;
	   END
     else BEGIN
	   tfshow(host,fileName,mode);
	   tfprint := 1;
	   END;
END;

PROCEDURE tfdone(success:integer);
BEGIN
     IF success <> 0
	   THEN begin
	   Msg := 'Successful transfer';
	   Message(StrCvt('File transfer done.'),@Msg);
	   end;
     { Print nothing in case of error }
END;

PROCEDURE DoServer(Dummy:PTR);
BEGIN
      tfsinit(@tfprint,@tfdone);
      tfs_on;
      WHILE (ServerEnabled) DO tk_yield; { Keep acting as server until disabled }
      tk_exit;
END;

PROCEDURE FixSetMenu;
VAR i:Integer;
BEGIN
       { Fix up the check marks on the menu }
       FOR i := 1 TO 4 DO CheckItem(MyMenus[MSetting],i,FALSE);
       case ModeToUse of
       ASCII:   CheckItem(MyMenus[MSetting],1,TRUE);
       IMAGE:   CheckItem(MyMenus[MSetting],2,TRUE);
       OCTET:   CheckItem(MyMenus[MSetting],3,TRUE);
       MACINTOSH:CheckItem(MyMenus[MSetting],4,TRUE)
       end;
END;

FUNCTION SInitDialog: Boolean;
CONST   OKBut = 1;
	CancelBut = 2;
	GetBox = 3;
	PutBox = 4;
	SInitDlog = 50;
VAR itemHit:INTEGER;
    MyDialog:DialogPtr;
    itemType:INTEGER;
    itemHndl:Handle;
    itemBox:Rect;
BEGIN
	SGOK := true;
	SPOK := false;
	MyDialog := GetNewDialog(SInitDlog,NIL,POINTER(-1));
	GetDItem(MyDialog,GetBox,itemType,itemHndl,itemBox);
	SetCtlValue(POINTER(ORD4(itemHndl)),1);
	GetDItem(MyDialog,PutBox,itemType,itemHndl,itemBox);
	SetCtlValue(POINTER(ORD4(itemHndl)),0);
	ModalDialog(NIL,itemHit);
	while not (itemHit in [OKBut, CancelBut]) do begin
		GetDItem(MyDialog,itemHit,itemType,itemHndl,itemBox);
		case itemHit of
		GetBox: BEGIN
			SGOK := NOT SGOK;
			SetCtlValue(POINTER(ORD4(itemHndl)),ORD(SGOK));
			END;
		PutBox: BEGIN
			SPOK := NOT SPOK;
			SetCtlValue(POINTER(ORD4(itemHndl)),ORD(SPOK));
			END;
		end; { case }
		ModalDialog(NIL, ItemHit);
		end;
	DisposDialog(MyDialog);
	if itemHit = CancelBut then SInitDialog := false
	else SInitDialog := true;
END;

FUNCTION NameRemFile(root:STR255; VAR oname:STR255; VAR host:STR255):Boolean;
CONST   OKBut = 1;
	CancelBut = 2;
	FileItem = 3;
	HostItem = 4;
	ASCIIitem = 5;
	IMAGEitem = 6;
	OCTETitem = 7;
	MACitem = 8;
VAR itemHit:INTEGER;
    MyDialog:DialogPtr;
    itemType:INTEGER;
    itemHndl:Handle;
    itemBox:Rect;

   PROCEDURE ResetButton(val:INTEGER);
   VAR tmp:INTEGER;
   begin
	case ModeToUse of
	ASCII:	   tmp := ASCIIitem;
	IMAGE:	   tmp := IMAGEitem;
	OCTET:	   tmp := OCTETitem;
	MACINTOSH: tmp := MACitem;
	end;
	GetDItem(MyDialog,tmp,itemType,itemHndl,itemBox);
	SetCtlValue(POINTER(ORD4(itemHndl)),val);
   end;

BEGIN
   if root = '' then begin
	ParamText('Name of The Directory','','','');
	end
   else begin
	oname := concat(Directory,root);
	ParamText('Name of The Remote File','','','');
	end;
   MyDialog := GetNewDialog(RFileDialog,NIL,POINTER(-1));
   GetDItem(MyDialog,HostItem,itemType,itemHndl,itemBox);
   SetIText(itemHndl,DestHostName);
   GetDItem(MyDialog,FileItem,itemType,itemHndl,itemBox);
   SetIText(itemHndl,oname);
   SelIText(MyDialog,FileItem,0,16000);
   ResetButton(1);
   ModalDialog(NIL,itemHit);
   while not (itemHit in [1,2]) do
	begin
	ResetButton(0);
	case itemHit of
	ASCIIitem:      ModeToUse := ASCII;
	IMAGEitem:      ModeToUse := IMAGE;
	OCTETitem:      ModeToUse := OCTET;
	MACitem:        ModeToUse := MACINTOSH;
	end;
	GetDItem(MyDialog,itemHit,itemType,itemHndl,itemBox);
	SetCtlValue(POINTER(ORD4(itemHndl)),1);
	ModalDialog(NIL,itemHit);
	end;
   if itemHit = 2 then
	begin
	DisposDialog(MyDialog);
	FixSetMenu;
	NameRemFile := false;
	exit(NameRemFile);
	end;
   GetDItem(MyDialog,HostItem,itemType,itemHndl,itemBox);
   GetIText(itemHndl,host);
   GetDItem(MyDialog,FileItem,itemType,itemHndl,itemBox);
   GetIText(itemHndl,oname);
   DisposDialog(MyDialog);
   FixSetMenu;
   NameRemFile := true;
END;

VAR UserSuccess:Integer;

PROCEDURE UserDone(success:Integer);
BEGIN
       tk_wake(UserTask);
       UserSuccess := success;
END;

PROCEDURE DoTransfer;
VAR
	TotalDeltat: LongInt;
	ADummy: INTEGER;
BEGIN
  fhost := resolve_name(DestHostName);
  IF fhost = 0 THEN BEGIN
     Error2(StrCvt('TFTP: Couldn''t resolve host name '),@DestHostName);
     exit(DoTransfer);
     END;
  IF fhost = NAMETMO THEN BEGIN
     Error(StrCvt('TFTP: Name servers not responding'));
     exit(DoTransfer);
     END;

  tfshow(fhost,@LocalFileName,Userdirection);

  totalDeltat := tickCount;
  if tftpuse(fhost,@LocalFileName,LocalVolume,@DestFileName,Userdirection,
	     ModeToUse,@UserDone) = 0 then begin
	     UserSuccess := 0;
	     exit(DoTransfer);
	     end;

  tk_block;

  Totaldeltat := (tickCount - totalDeltat) DIV TPS;

  if userdirection = GET then ADummy := ORD(flushvol(NIL,LocalVolume));

  if UserSuccess <> 0 then begin
     Msg := 'File transferred in ';
     NumToStr(Totaldeltat,TempText);
     insert(TempText,Msg,length(Msg)+1);
     insert(' seconds.',Msg,length(Msg)+1);
     Message(StrCvt('File transfer successful.'),@Msg);
     end;
END;

{dummy procedures to force loading of the segments they contain}
{$S UDPNameS} PROCEDURE NameLoad; BEGIN END;
{$S UDPSeg  } PROCEDURE UDPLoad; BEGIN NameLoad END;
{$S TFTPSeg }

PROCEDURE DoUser(Dummy:PTR);
VAR
	Pnt:Point;
	Reply:SFReply;
	SFDummy:SFTypeList;
	Files:StrLHandle;
	i:Integer;
BEGIN
    Pnt.h := 100;
    Pnt.v := 90;
    WHILE TRUE DO BEGIN
    UserRunning := FALSE;
    tk_block;
    UserRunning := TRUE;
    DisableItem(MyMenus[MCmd],1);      { turn off get }
    DisableItem(MyMenus[MCmd],2);      { turn off put }
    EnableItem(MyMenus[MCmd],4);       { turn on abort }
    { these next three statements are intended to prevent the need to swap disks
      if the disk with TFTP is ejected }
    CouldDialog(RFileDialog);
    CouldDialog(errID);
    UDPLoad;
    if UserDirection = GET then begin
	  SFPutFile(Pnt,'Local File Name:',LocalFileName,NIL,Reply);
	  if Reply.good then
		  begin
		  LocalFileName := Reply.fName;
		  DestFileName := Reply.fName;
		  LocalVolume := Reply.vRefNum;
		  if NameRemFile(LocalFileName,DestFileName, DestHostName)
		  then DoTransfer;
		  end;
	  end
    else begin { UserDirection = PUT }
	  Files := SFMGetFile(Pnt,NIL,-1,SFDummy,NIL,BitOR(SFMmulti,SFMenable));
	  if Files <> NIL then begin
		  if Files^^.howmany = 1 then begin
			  { just one file selected }
			  LocalFileName := Files^^.table[0].str^^;
			  DestFileName := Files^^.table[0].str^^;
			  LocalVolume := Files^^.volume;
			  if NameRemFile(LocalFileName,DestFileName,DestHostName)
			  then DoTransfer;
			  end
		  else begin { more than one file selected }
			if NameRemFile('',Directory,DestHostName) then begin
				for i := 0 to Files^^.howmany-1 do begin
				    LocalFileName := Files^^.table[i].str^^;
				    DestFileName := concat(Directory,
						    Files^^.table[i].str^^);
				    LocalVolume := Files^^.volume;
				    DoTransfer;
				    if UserSuccess = 0 then leave;
				    end; { for }
				end; {if NameRemFile}
			end; { multiple files }
		  DelFList(Files); { clean up from SFMGetFile }
		  end; { if Files <> NIL }
	   SFMEnd;
	   end; { PUT }
    FreeDialog(RFileDialog);
    FreeDialog(errID);
    EnableItem(MyMenus[MCmd],1); { turn on get }
    EnableItem(MyMenus[MCmd],2); { turn on put }
    DisableItem(MyMenus[MCmd],4);{ turn off abort }
    END; { endless while }
END;

{$S InitSeg}

PROCEDURE SetUpMenus;
{ Once-only initialization for menus }
VAR
      i: INTEGER;
      appleTitle: STRING[1];
BEGIN
   InitMenus; { initialize Menu Manager }
   appleTitle := ' '; appleTitle[1] := CHR(20);
   myMenus[1] := NewMenu(appleMenu,appleTitle);
   AddResMenu(myMenus[1],'DRVR'); { desk accessories }
   myMenus[MCmd] := GetMenu(CmdMenu);
   myMenus[MSetting] := GetMenu(SettingMenu);
   myMenus[MShow] := GetMenu(ShowMenu);
   FOR i := 1 TO lastMenu DO InsertMenu(myMenus[i],0);
   DrawMenuBar;
   DisableItem(MyMenus[MCmd],4); { turn off abort }
END; { of SetUpMenus }

{$S	  }

PROCEDURE DoCommand(mResult: LongInt);
CONST
      FDirDialog = 60;
      DirItem = 3;
VAR
  i: Integer;
  ItemHndl:Handle;
  myNode,myNet:INTEGER;
  itemHit:INTEGER;
  MyDialog:DialogPtr;
  itemType:INTEGER;
  itemBox:Rect;

BEGIN
   theMenu := HiWord(mResult);
   theItem := LoWord(mResult);
   CASE theMenu OF

      appleMenu:
	 BEGIN
	 GetItem(myMenus[1],theItem,TempText);
	 refNum := OpenDeskAcc(TempText);
	 END;

      CmdMenu:
	 BEGIN
	 CASE theItem OF
	    1,2: BEGIN
		  DisableItem(MyMenus[MCmd],3); { turn off server }
		  IF UserTask = NIL THEN BEGIN
		     UserTask := tk_fork(Main_Task,@DoUser, MYTKSZ,
					 'DoUser',NIL);
		     tk_yield;	  { Give initial wakeup }
		     END;
		  IF theItem = 1 THEN UserDirection := GET
		  ELSE UserDirection := PUT;
		  tk_wake(UserTask);
		  END;

	     3: BEGIN
		    if SInitDialog then begin
			  ServerEnabled := TRUE;
			  Server_Task := tk_fork(Main_Task,@DoServer,MYTKSZ,
						 'Server',NIL);
			  { Disable all commands but Quit }
			  CheckItem(MyMenus[MCmd],ServerItem,TRUE);
			  DisableItem(MyMenus[MSetting],0);
			  DisableItem(MyMenus[MCmd],1);
			  DisableItem(MyMenus[MCmd],2);
			  DisableItem(MyMenus[MCmd],3);
			  end;
		END;

	     4: tfabort;

	     5: doneFlag := TRUE;
	 END; { of item case }
	 END; { of CmdMenu }

      SettingMenu:
	 BEGIN
	 CASE theItem OF
	     1: ModeToUse := ASCII;
	     2: ModeToUse := IMAGE;
	     3: ModeToUse := OCTET;
	     4: ModeToUse := MACINTOSH;

	     5: { Remote Host }
		if NameRemoteHost(TempText) then DestHostName := TempText;

	     6: BEGIN	  { Remote Directory }
		MyDialog := GetNewDialog(FDirDialog,NIL,POINTER(-1));
		GetDItem(MyDialog,DirItem,itemType,itemHndl,itemBox);
		SetIText(itemHndl,Directory);
		SelIText(MyDialog,DirItem,0,16000);
		ModalDialog(NIL,itemHit);
		if itemHit = 1 then GetIText(itemHndl,Directory);
		DisposDialog(MyDialog);
		END;
	 END; { End of item case }
	 FixSetMenu;

	 END; { of SettingMenu }

	  ShowMenu:
	     begin
	     case theItem of
	     1: BEGIN { Show UDP statistics }
		  udp_table;
		END;

	     2:	 { Address of my machine }
		 begin
		 cvt_inaddr(in_mymach(0),Msg);
		 Message(StrCvt('My Internet address:'),@Msg);
		 end;

	     3: { Show Local Appletalk Address }
		if GetNodeAddress(myNode,myNet) = noErr then
		  begin
		  Msg := 'Network: ';
		  NumToStr(myNet,TempText);
		  insert(TempText,Msg,length(Msg)+1);
		  insert(', Node: ',Msg,length(Msg)+1);
		  NumToStr(myNode,TempText);
		  insert(TempText,Msg,length(Msg)+1);
		  Message(StrCvt('My Appletalk address:'),@Msg);
		END;

	     4:	  begin
		  in_stats;
		  end;
	     END; { case theItem of }
	  END; { ShowMenu }
   END; { case theMenu of }
   HiliteMenu(0);

END; { of DoCommand }

{$S InitSeg }

FUNCTION StackBase:LongInt;
CONST CurStackBase = $908;      { system global }
TYPE LongPtr = ^LongInt;
BEGIN
	StackBase := LongPtr(CurStackBase)^;
END;

PROCEDURE InitNetwork;
BEGIN
      MainStack := 4096;
      IPStack := 4096;
      SetApplLimit(POINTER(StackBase -
		   (MainStack + IPStack + ARPTKSZ + TFTKSZ + MYTKSZ + 2048)));
      MaxApplZone;
      MoreMasters; MoreMasters; MoreMasters; MoreMasters; MoreMasters;

      InitGraf(@thePort);
      InitFonts;
      FlushEvents(everyEvent,0);
      InitWindows;
      SetUpMenus;
      InitDialogs(NIL);
      InitCursor;
      TEInit;

      screen := screenBits.bounds;
      SetRect(dragRect,4,24,screen.right-4,screen.bottom-4);

      ErrInit;
      if MPPOpen <> noErr then
	     Fatal(StrCvt('Can''t open Mac Protocol Package'),false);
      Main_Task := tk_init(mainstack);
      tm_init;
      in_init;
      IcmpInit;
      GgpInit;
      UdpInit;
      tftpinit;

     { end of Init_IP }

      INFOMSGChked := True;
      NETERRChked  := True;
      PROTERRChked := True;
      ServerEnabled := False;
      LocalFileName := '';
      DestFileName := '';
      Directory := '';
      DestHostName := DefaultHost;
      ModeToUse := ASCII;
      CheckItem(MyMenus[MSetting],1,true);
      tftpnum := 0;
      IPuninited := TRUE;
      server_task := NIL;
      UserTask := NIL;
      UserRunning := FALSE;
      doneFlag := FALSE;
END;

{$S	   }

PROCEDURE MainEventLoop;
CONST
     NetworkEvt = 10;
VAR  dp:DialogPtr;
     itemHit:INTEGER;
BEGIN

   REPEAT
      SystemTask;

      if GetNextEvent(everyEvent,myEvent) then begin

      if IsDialogEvent(myEvent) then
	     begin
	     if NOT DialogSelect(myEvent,dp,itemHit) then
		     begin
		     if (myEvent.what = mouseDown) then
			     begin
			     IF NOT ClickResolve(myEvent) THEN ;
			     end
		     else if (myEvent.what = activateEvt) then
			     begin
			     if NOT ActResolve(myEvent) then ;
			     end;
		     cycle;
		     end;
	     end;

      CASE myEvent.what OF

	 mouseDown:
	    BEGIN
	    code := FindWindow(myEvent.where,whichWindow);
	    CASE code OF

	       inMenuBar: DoCommand(MenuSelect(myEvent.where));

	       inSysWindow: SystemClick(myEvent,whichWindow);

	       inDrag: DragWindow(whichWindow,myEvent.where,dragRect);

	       inGrow,inContent:
		  BEGIN
		  IF whichWindow <> FrontWindow THEN
		     SelectWindow(whichWindow)
		  END;

	    END; { of code case }
	    END; { of mouseDown }

	 keyDown,autoKey: SysBeep(2);
      END; { of event case }
   END { of if GetNextEvent }

   ELSE IF myEvent.what = nullEvent THEN tk_yield;

   UNTIL doneFlag;
END;

BEGIN { main program }
   InitNetwork;
   UnLoadSeg(@InitNetwork);
   MainEventLoop;
   in_close;
   tm_allFree;
END.
!E!O!F!
#
#
echo extracting appl/tftpo.text...
cat >appl/tftpo.text <<'!E!O!F!'
appl-tftpo.rsrc

Type CODE
  appl-tftpl,0
!E!O!F!
#
#
echo extracting appl/tftpr.text...
cat >appl/tftpr.text <<'!E!O!F!'
*  TftpResDef -- Resource input for TFTP program
*
*{ Please note the copyright notice in the file "copyright/notice" }

appl-tftpr.Rsrc

Type MENU

  ,256
  File
    Get...
    Put...
    Server...
    Abort
    Quit

   ,257
   Settings
     ASCII Mode
     IMAGE Mode
     OCTET Mode
     MACINTOSH Mode
     Remote Host...
     Remote Directory...

   ,258
   Show
    UDP Statistics
    My Internet Address
    My Appletalk Address
    IP Statistics

Type DLOG
 ,11
 90 75 260 425
 Visible 1 NoGoAway 0
 17

Type DITL
  ,17
  10
  BtnItem Enabled
    114 15 132 160
OK

  BtnItem Enabled
    114 190 132 335
Cancel

  EditText Disabled
    34 15 50 335
Put Name Here

  EditText Disabled
    84 15 100 335
Put Host Here

RadioItem Enabled
    150 15 165 90
ASCII

RadioItem Enabled
    150 95 165 170
Image

RadioItem Enabled
   150 175 165 250
Octet

RadioItem Enabled
   150 255 165 330
Mac

  StatText Disabled
    12 15 28 335
^0:

  StatText Disabled
    62 15 78 335
Name or Address of the Remote Host:

Type DLOG
  ,50
  100 100 215 400
  Visible 2 NoGoAway 0
  52

Type DITL
  ,52
  5
BtnItem Enabled
  65 34 97 114
OK

BtnItem Enabled
  65 180 97 260
CANCEL

ChkItem Enabled
  35 35 52 115
Fetch

ChkItem Enabled
  35 180 51 280
Store

StatText Disabled
  10 35 31 280
Indicate which transfers to allow:

Type DLOG
  ,49
  100 125 245 375
  Invisible 3 NoGoAway 0
  51
Error Box

Type DITL
  ,51
  2
  StatText Disabled
    10 10 25 240
A Network Error Happened!

  StatText Disabled
    80 10 135 240
Set by SetIText

Type DLOG
 ,60
 100 80 190 420
 Visible 1 NoGoAway 0
 61
Foreign Directory

Type DITL
  ,61
  4
  BtnItem Enabled
    10 95 30 155
OK

  BtnItem Enabled
    10 185 30 245
CANCEL

  EditText Disabled
     65 10 80 330
dir

  StatText Disabled
     40 10 55 330
Please type in the foreign directory name.

Type DLOG
  ,-4001
  0 0 152 358
  InVisible 1 NoGoAway 0
  -4001

Type DITL
  ,-4001
  12
  BtnItem  Enabled
  28 162 46 242
  Open

  BtnItem Disabled
  59 1552 77 1232
  Invisible

  BtnItem  Enabled
  90 162 108 242
  Cancel

*the volume name
  UserItem Disabled
  22 258 54 354

  BtnItem Enabled
  59 266 77 346
  Eject

  BtnItem Enabled
  90 266 108 346
  Drive

*the window in the dialog
  UserItem Enabled
  11 12 125 135

*the scroll bar for the window
  UserItem Enabled
  11 134 125 150

*the gray line
  UserItem Disabled
  20 254 116 255

  StatText Disabled
  20 1044 116 1145
Invisible Text

*the horizontal scroll bar
  UserItem Enabled
  124 12 140 135

  ChkItem Enabled
  125 172 140 346
Multiple File Selection

Type DLOG
 ,31
 100 125 245 375
 Visible 1 NoGoAway 0
 62
Name Foreign Host

Type DITL
  ,62
  4
  BtnItem Enabled
    10 50 40 110
OK

  BtnItem Enabled
    10 140 40 200
CANCEL

  EditText Disabled
     115 10 130 240
DefaultForeignHost

  StatText Disabled
     50 10 100 240
Please type in the name or number of the foreign host you want to reach.

Type ALRT
  ,333
  80 100 255 420
  444
  FFFF

Type DITL
  ,444
  3
  BtnItem Enabled
    140 110 165 210
QUIT

  StatText Disabled
    10 90 55 300
A fatal error happened!	 The program will die.

  StatText Disabled
    80 20 135 300
^0

Type ALRT
  ,666
  80 100 255 420
  777
  FFFF

Type DITL
  ,777
  5
  BtnItem Enabled
    140 110 165 210
HALT

  StatText Disabled
    10 90 55 300
Fatal Error! A task overflowed its stack!

  StatText Disabled
    80 20 95 300
Stack Pointer about ^0

  StatText Disabled
    100 20 115 300
Task control block at ^1

  StatText Disabled
    120 20 135 300
Task name is ^2

Type DLOG
  ,77
  50 100 295 375
  Visible 4 NoGoAway 0
  78
IP Statistics

Type DITL
  ,78
  22
  StatText Disabled
    15 180 30 265
precv

  StatText Disabled
    35 180 50 265
psnt

  StatText Disabled
    55 180 70 265
pdrop

  StatText Disabled
    75 180 90 265
bdchk

  StatText Disabled
    95 180 110 265
unprot

  StatText Disabled
    115 180 130 265
bdvers

  StatText Disabled
    135 180 150 265
bdlen

  StatText Disabled
    155 180 170 265
ttlexp

  StatText Disabled
    175 180 190 265
frags

  StatText Disabled
    195 180 210 265
free

  StatText Disabled
    215 180 230 265
fail

  StatText Disabled
    15 10 30 170
Packets Received:

  StatText Disabled
    35 10 50 170
Packets Sent:

  StatText Disabled
    55 10 70 170
Packets Dropped:

  StatText Disabled
    75 10 90 170
Bad Checksums:

  StatText Disabled
    95 10 110 170
Unhandled Protocols:

  StatText Disabled
    115 10 130 170
Bad Versions:

  StatText Disabled
    135 10 150 170
Bad Lengths:

  StatText Disabled
    155 10 170 170
TTL Expired:

  StatText Disabled
    175 10 190 170
Fragments:

  StatText Disabled
    195 10 210 170
Free Packets:

  StatText Disabled
    215 10 230 170
Listen Failures:

Type DLOG
  ,93
  100 75 250 425
  Visible 4 NoGoAway 0
  94
UDP Statistics

Type DITL
  ,94
  15
  StatText Disabled
    10 10 25 70
Local

  StatText Disabled
    10 78 25 138
Foreign

  StatText Disabled
    10 146 25 206
Host

  StatText Disabled
    10 214 25 276
Handler

  StatText Disabled
    10 284 25 344
Cn

  StatText Disabled
    30 10 45 70


  StatText Disabled
    30 78 45 138


  StatText Disabled
    30 146 45 206


  StatText Disabled
    30 214 45 276


  StatText Disabled
    30 284 45 344


  StatText Disabled
    50 10 65 70


  StatText Disabled
    50 78 65 138


  StatText Disabled
    50 146 65 206


  StatText Disabled
    50 214 65 276


  StatText Disabled
    50 284 65 344


Type BNDL
  ,128
  TFTP 0
  2
  ICN# 1
  0 128
  FREF 1
  0 128

Type FREF
  ,128
  APPL 0

Type TFTP = STR
  ,0
TFTP Version 2.2, 10 September 1985

!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting net/arp_lib.text...
cat >net/arp_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
Unit ARP_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

{
Note: This code was inspired by the Ethernet device handler in the
PCIP system distributed by MIT.
}

INTERFACE

{$L-}

   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-IP_Lib	  } IP_Lib;

{$L+}

CONST
	MaxIPAddr = 10;

	ARP_Request     =       1;
	ARP_Reply       =       2;
	ARP_Hrd_AB      =       3; { This magic number created by Mark Sherman
				     and is not official }

	AB_ARP	        =       23;{ Address resolution protocol, as per
				     Ethernet and RFC 826. However, ethernet uses
				     protocol number 2054 to represent an ARP
				     Protocol - I can not since DDP only has 8
				     bits of protocol number. }

	AR_DOD_IP       =       2048; { Magic number requesting translation to
					/from DOD internet protocol }

TYPE
	ARP_Buf = PACKED RECORD
			AR_Hrd: Integer;        { Hardward address space }
			AR_Pro: Integer;        { Protocol address space }
			AR_Hln: Byte;	        { Byte len of hardware add = 4 }
			AR_Pln: Byte;	        { Byte len of protocol add = 4 }
			AR_Op:  Integer;        { Opcode (request or reply) }
			AR_Snd_AB: AddrBlock;   { Hardware address of sender }
			AR_Snd_IP: in_name;     { Protocol address of sender }
			AR_Tar_AB: AddrBlock;   { Hardware address of target }
			AR_Tar_IP: in_name;     { Protocol address of target }
			END;

	ARP_Packet = ^ ARP_Buf;

	IP2AB_Entry = RECORD
			AB: AddrBlock;
			IP: in_name;
			Count: Integer; { try to keep some statistics }
			END;

FUNCTION arp_init: Ref_Task;
PROCEDURE Add_AB_Address(IP_Address:in_name;AB_Addr:AddrBlock);
PROCEDURE AB_ARP_Snd(WhichWay:Integer; Recipient: AddrBlock;
		    Other_IP_Addr: in_name);
PROCEDURE AB_ARP_Rcv(Dummy:PTR);
FUNCTION Get_AB_Address(IP_Address:in_name;VAR AB_Addr:AddrBlock): Boolean;
PROCEDURE IP2AB(IP: in_name; VAR AB:AddrBlock);

CONST	  ARPTKSZ = 2048;

IMPLEMENTATION

VAR
	ARPtask: Ref_Task;	       { main ARP receiving task }
	Cur_ARP_Pkt:ARP_Packet;	       { ARP receive packet }
	arp_rcv: Ref_Task;	       { Task that waits for incoming ARPs }

	my_AB_Addr: AddrBlock;	       { My Apple bus address }
	Everyone_on_AB: AddrBlock;     { A reusable broadcast address }

	my_ARP_Pkt: ARP_Packet;	       { Reusable send packet for ARP }

	{ Table of IP <-> AB Pairs }
	IP2AB_Table: ARRAY [0..MaxIPAddr] OF IP2AB_Entry;
	Last_IP2AB_Entry: -1..MaxIPAddr; { Last Entry Filled In }
	Next_IP2AB_Entry: 0..MaxIPAddr; { Next Entry to Use when inserting entry }

	{ Statistics on ARP Usage }

	ABARPSnd: Integer;	        { ARPs sent }
	ABARPReq: Integer;	        { ARP requests received }
	ABARPRep: Integer;	        { ARP replies received }
	ABARPNotMe: Integer;	        { Misaddress ARPs - should be 0 w/ DDP }
	ABARPBad: Integer;	        { ARP Packet ill formed }
	ABARPUnexpected: Integer;       { Got ARP when Request not outstanding }
	ABARPProtocolErr: Integer;      { ARP wanted non IP address translation }

{$S InitSeg }

PROCEDURE ARPEnable; external;
FUNCTION GetARPBuf:ARP_Packet; external;

FUNCTION arp_init: Ref_Task;
VAR     myNode,myNet:INTEGER;
BEGIN
	ABARPSnd := 0;
	ABARPReq := 0;
	ABARPRep := 0;
	ABARPNotMe := 0;
	ABARPBad := 0;
	ABARPUnexpected := 0;
	ABARPProtocolErr := 0;

	if GetNodeAddress(myNode,myNet) <> noErr then exit(arp_init);
	my_AB_Addr.ANode := myNode;
	my_AB_Addr.ANet := myNet;
	my_AB_Addr.ASocket := AB_IP_Socket;

	Everyone_on_AB.ANet := 0;
	Everyone_on_AB.ANode := $FF;
	Everyone_on_AB.ASocket := AB_IP_Socket;

	Last_IP2AB_Entry := -1;
	Next_IP2AB_Entry := 0;

	{ ARP initialization }
	arp_rcv := NIL;
	my_ARP_Pkt := POINTER(ORD4(NewPtr(sizeof(ARP_Buf))));
	cur_ARP_Pkt := getarpbuf;

	ARPtask := tk_fork(tk_cur,@AB_ARP_Rcv,ARPTKSZ,'ARP Recv',NIL);
	arp_init := ARPTask;
END;

{$S	    }

{$IFC DEBUG}
PROCEDURE out_inaddr(fhost: in_name);
VAR host:_ipname;
BEGIN
	host.in_lname := fhost;
	Write(BitAND(host.in_lst.in_net,255):1,'.',
	      BitAND(host.in_lst.in_nets,255):1,'.',
	      BitAND(host.in_lst.in_netss,255):1,'.',
	      BitAND(host.in_lst.in_host,255):1);
END; { End of out_inaddr }
{$ENDC}

PROCEDURE Add_AB_Address(IP_Address:in_name;AB_Addr:AddrBlock);
{ Look in the table for the appropriate ip address. If there, reset AB_Addr
  to it, otherwise make a new entry }
VAR i: Integer;
BEGIN
{$IFC DEBUG}
    Write('Add_AB_Address: Associating IP address ');
    out_inaddr(IP_Address);WriteLn(' with AB address ',AB_Addr.ANode:1);
{$ENDC}

    FOR i := 0 TO Last_IP2AB_Entry DO
	IF IP2AB_Table[i].IP = IP_Address THEN BEGIN
	    IP2AB_Table[i].AB := AB_Addr;
	    EXIT(Add_AB_Address);
	    END;

    { Hmm, not in table already, must make a new entry }
    IP2AB_Table[Next_IP2AB_Entry].IP := IP_Address;
    IP2AB_Table[Next_IP2AB_Entry].AB := AB_Addr;
    Next_IP2AB_Entry := Next_IP2AB_Entry + 1;
    IF Next_IP2AB_Entry > MaxIPAddr THEN Next_IP2AB_Entry := 0;
    IF Last_IP2AB_Entry <> MaxIPAddr THEN Last_IP2AB_Entry := Last_IP2AB_Entry + 1;

END;

PROCEDURE AB_ARP_Snd(WhichWay:Integer; Recipient: AddrBlock;
		    Other_IP_Addr: in_name);
	{Always use the same packet to send }
VAR
	Success: Integer; { Status DDP write - ignored for ARP }
	myNode,myNet:INTEGER;
BEGIN
{$IFC DEBUG}
	WriteLn('AB_ARP_Snd: About to send an ARP packet');
{$ENDC}
	WITH my_ARP_Pkt^ DO BEGIN
	    AR_Hrd:= ARP_Hrd_AB;
	    AR_Pro:= AR_DOD_IP;
	    AR_Hln:= sizeof(AddrBlock);
	    AR_Pln:= sizeof(in_name);
	    AR_Op:= WhichWay;
	    { Should not be necessary but we are paranoid }
	    if GetNodeAddress(myNode,myNet) <> noErr then exit(AB_ARP_Snd);
	    my_AB_Addr.ANode := myNode;
	    my_AB_Addr.ANet := myNet;
	    my_AB_Addr.ASocket := AB_IP_Socket;
	    AR_Snd_AB:= my_AB_Addr;
	    AR_Snd_IP:= LocalIPaddr;
	    AR_Tar_AB:= Recipient;
	    AR_Tar_IP:= Other_IP_Addr;
	    END;

{$IFC DEBUG}
	WriteLn('The Write call is using socket ',my_AB_Addr.ASocket);
	WriteLn('With buffer length ',sizeof(ARP_Buf));
	WriteLn('Going to node ',Recipient.ANode);
{$ENDC}

	{ Wait until previous write finished }
	WHILE CurW_Hdl^^.abResult = 1 DO tk_yield;
	{ Fill in the disk block }
	WITH CurW_Hdl^^ DO BEGIN
	    ddpType := AB_ARP;
	    ddpSocket := my_AB_Addr.ASocket;
	    ddpAddress := Recipient;
	    ddpReqCount := sizeof(ARP_BUF);
	    ddpDataPtr := POINTER(ORD4(my_ARP_Pkt));
	    END;
	{ And do the write }
	Success := DDPWrite(CurW_Hdl,false,true);

{$IFC DEBUG}
	WriteLn('AB_ARP_Snd: Wrote packet on applebus, status = ',Success);
{$ENDC}
END;

PROCEDURE AB_ARP_Rcv(Dummy:PTR);
LABEL 777;
BEGIN
    WHILE TRUE DO BEGIN
777:
	ARPEnable;
	tk_block;

{$IFC DEBUG}
	WriteLn('AB_ARP_Rcv: Received an ARP packet');
{$ENDC}

	WITH Cur_ARP_Pkt^ DO BEGIN
{$IFC DEBUG}
	WriteLn('AR_Hrd = ',AR_Hrd);
	WriteLn('AR_Pro = ',AR_Pro);
	WriteLn('AR_Hln = ',AR_Hln);
	WriteLn('AR_Pln = ',AR_Pln);
	WriteLn('AR_Op  = ',AR_Op);
	WriteLn('AR_Snd_AB = ',AR_Snd_AB.ANode);
	Write('AR_Snd_IP = ');out_inaddr(AR_Snd_IP);WriteLn('');
	WriteLn('AR_Tar_AB = ',AR_Tar_AB.ANode);
	Write('AR_Tar_IP = ');out_inaddr(AR_Tar_IP);WriteLn('');
{$ENDC}

       { Wants hardware translation for Apple Bus? }
       IF AR_Hrd <> ARP_Hrd_AB THEN BEGIN
{$IFC DEBUG}
	   WriteLn('AB_ARP_Rcv: Doesn''t want AB hardware: ',AR_Hrd);
{$ENDC}

	   ABARPProtocolErr := ABARPProtocolErr + 1;
	   goto 777;
	   END;

       { Wants protocol translation of DOD's IP? }
       IF AR_Pro <> AR_DOD_IP THEN BEGIN
{$IFC DEBUG}
	   WriteLn('AB_ARP_Rcv: Doesn''t want IP translation: ',AR_Pro);
{$ENDC}
	   ABARPProtocolErr := ABARPProtocolErr + 1;
	   goto 777;
	   END;

       { Wants *my* IP address to be translated into an AB address? }
       IF AR_Tar_IP <> LocalIPaddr THEN BEGIN
{$IFC DEBUG}
	   Write('AB_ARP_Rcv: Not my IP address ');out_inaddr(AR_Tar_IP);WriteLn;
{$ENDC}
	   ABARPNotMe := ABARPNotMe + 1;
	   goto 777;
	   END;

       { Everything OK, so first update my tables with his information }
       Add_AB_Address(AR_Snd_IP,AR_Snd_AB);

       CASE AR_OP OF

	    ARP_Request: BEGIN

{$IFC DEBUG}
		Write('AB_ARP_Request: Got an ARP Request for me from ');
		out_inaddr(AR_Snd_IP); WriteLn('');
{$ENDC}
		ABARPReq := ABARPReq + 1;

		{ And answer the request }
		AB_ARP_Snd(ARP_Reply, AR_Snd_AB, AR_Snd_IP);
		END; { End of ARP_Request }

	    ARP_Reply: BEGIN
{$IFC DEBUG}
		Write('AB_ARP_Request: Got an ARP Reply for me from ');
		out_inaddr(AR_Snd_IP); WriteLn('');
{$ENDC}
		{ Someone waiting for the reply ? }
		IF arp_rcv <> NIL
		    THEN tk_wake(arp_rcv)
		    ELSE ABARPUnexpected := ABARPUnexpected + 1; { Why arrived? }

		arp_rcv := NIL; { Make sure he is not awakened more than once }
		END; { End of ARP_Reply }

	    OTHERWISE BEGIN
		ABARPBad := ABARPBad + 1;
{$IFC DEBUG}
		WriteLn('Bad opcode in ARP packet: ',AR_Op);
{$ENDC}
		END;

	END; { of CASE }
	END; { of WITH }
    END; { infinite loop }
END;

FUNCTION Get_AB_Address(IP_Address:in_name;VAR AB_Addr:AddrBlock): Boolean;
{ Look in the table for the appropriate ip address. If there, set AB_Addr
  to it and return true, otherwise return false }
VAR i: Integer;
BEGIN
    FOR i := 0 TO Last_IP2AB_Entry DO
	IF IP2AB_Table[i].IP = IP_Address THEN BEGIN
	    AB_Addr := IP2AB_Table[i].AB;
	    Get_AB_Address := true;
	    EXIT(Get_AB_Address);
	    END;
    Get_AB_Address := False;
END;

PROCEDURE ARP_dotimer(tk:Ref_Task);
BEGIN
     if (tk <> NIL) & (tk <> tk_cur) then tk_wake(tk);
END;

PROCEDURE IP2AB(IP: in_name; VAR AB:AddrBlock);
CONST
    ARP_TIME_OUT = 900;	 { 15 seconds = 900 ticks }
VAR
    ARPtimer: Ref_Timer;
    Dummy:BOOLEAN;
BEGIN

{$IFC DEBUG}
    Write('IP2AB: Looking up AB address for ');out_inaddr(IP);WriteLn('');
{$ENDC}

    { Look it up in the current table }
    IF Get_AB_Address(IP,AB) THEN EXIT(IP2AB);

{$IFC DEBUG}
    WriteLn('IP2AB: AB address not found so performing ARP');
{$ENDC}

    { Not there, so let's ARP for it }
    arp_rcv := tk_cur;
    AB_ARP_Snd(ARP_Request, Everyone_on_AB, IP);
    ARPtimer := tm_alloc;
    if ARPtimer = NIL then begin
{$IFC DEBUG}
	  WriteLn('IP2AB: Can''t allocate timer');
{$ENDC}
	  AB.ANet := 0;
	  AB.ASocket := 0;
	  AB.ANode := 0;
	  arp_rcv := NIL;
	  exit(IP2AB);
	  end;

    tm_tset(ARP_TIME_OUT,@arp_dotimer,POINTER(ORD4(arp_rcv)),ARPtimer);

    while true do begin
{$IFC DEBUG}
	  WriteLn('IP2AB: Blocking');
{$ENDC}
	  tk_block;
{$IFC DEBUG}
	  WriteLn('IP2AB: Awakened');
{$ENDC}
	  { Have we been awakened by a reception, or timed out? }
	  IF arp_rcv = NIL { got an ARP packet }
	    THEN BEGIN
		 Dummy := tm_clear(ARPtimer);
{$IFC DEBUG}
		 WriteLn('IP2AB: Got an ARP response');
{$ENDC}
		 IF Get_AB_Address(IP,AB) THEN BEGIN
			 Dummy := tm_free(ARPtimer);
			 EXIT(IP2AB)
			 END
		 ELSE BEGIN
		     { Nuts, got a different ARP answer, maybe ours is en route }
{$IFC DEBUG}
		     WriteLn('IP2AB: Received wrong ARP response');
{$ENDC}
		     tm_tset(ARP_TIME_OUT,@arp_dotimer,POINTER(ORD4(arp_rcv)),
			      ARPtimer);
		     arp_rcv := tk_cur;
		     END;
		 END { end of = NIL block }
	  ELSE BEGIN { Timed out. Oh well, we tried }
{$IFC DEBUG}
	       WriteLn('IP2AB: ARP timeout. Cannot translate address');
{$ENDC}
	       Dummy := tm_free(ARPtimer);
	       AB.ANet := 0;
	       AB.ASocket := 0;
	       AB.ANode := 0;
	       arp_rcv := NIL;
	       exit(IP2AB);
	       END;
	END; { end of WHILE loop }
END;

END.
!E!O!F!
#
#
echo extracting net/call_asm.text...
cat >net/call_asm.text <<'!E!O!F!'
;
;{ Please note the copyright notice in the file "copyright/notice" }
;
; The following little hack allows procedures to be called with any number
; of arguments so long as the procedure ptr is the last argument and so long
; as no static links are required to be maintained (Also works for functions!)
;


     .PROC CALL,4	      ; General interface for calling procedures
     .DEF  Call1
     .DEF  CallB
     .DEF  Call1B
     .DEF  Call1C
     .DEF  Call2
     .DEF  Call3
     .DEF  Call3A
     .DEF  Call3B
     .DEF  Call4
     .DEF  Call4A
CALLB
CALL1
CALL1A
CALL1B
CALL1C
CALL2
CALL3
CALL3A
CALL3B
CALL4
CALL4A
     MOVE.L    4(SP),A0	      ; Copy the procedure pointer
     MOVE.L    (SP)+,(SP)     ; Move the return address down
     JMP       (A0)	      ; Jump to the real routine

;
;    The following routine has the following signature:
;    FUNCTION StrCvt(S:STR255):StringPtr;
;
;    It is intended to take a string a return a pointer to that string.
;    Because Lisa Pascal will pass all strings over four characters in
;    length by passing a pointer to the string, and because the Lisa
;    Pascal conventions require the *caller* to make a copy of value
;    structures, one can pass the address of a string by just passing the
;    string.  This is a long way of saying that this routine just moves its
;    arguemnt down the stack
;
     .FUNC StrCvt,1	      ; Slimy way to pass String Constants by VAR
			      ; (Just leave the address on the stack!)
     MOVE.L    (SP)+,A0	      ; Get the return address
     MOVE.L    (SP)+,(SP)     ; Move the argument into the return value
     JMP       (A0)	      ; And return !
;
; The Lisa Pascal version of checksumming was wrong.  Here is an assembler
; version from Bill Croft at Stanford.
;
     .func     cksum	      ; (integer) sum = cksum(buf:Ptr, wdcnt:integer)
     move.l    (sp)+,a1
     clr.l     d1
     move.w    (sp)+,d1
     move.l    (sp)+,a0
     subq.l    #1,d1
     clr.l     d0
@1
     add.w     (a0)+,d0
     bcc.s     @2
     addq.l    #1,d0
@2
     dbra      d1,@1
     move.w    d0,(sp)
     jmp       (a1)
;
; Reboot routine scarfed from net.micro.mac
;
     .func     reboot
ROMStart .equ  $2AE
     move.l    ROMStart,A0
     add.w     #10,A0
     jmp       (A0)
;
     .END


!E!O!F!
#
#
echo extracting net/calls.text...
cat >net/calls.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
UNIT Call_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

INTERFACE

{$L-}

USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf;

{$L+}

PROCEDURE Call(Proc: ProcPtr);
FUNCTION  CallB(Proc:ProcPtr):Boolean;
PROCEDURE Call1(ArgPtr:PTR; Proc: ProcPtr);
PROCEDURE Call1B(Flag: Integer; Proc: ProcPtr);
PROCEDURE Call1C(c:char; Proc: ProcPtr);
PROCEDURE Call3(PacketPtr:PTR; PacketLength: Integer; SourceAdd: LongInt;
		Handler: ProcPtr);
FUNCTION  Call3A(host: LongInt; file_name: StringPtr;
		 TransferDirection: Integer; tfs_alert: ProcPtr): Integer;
PROCEDURE Call3B(what:PTR; arg2,arg3:Integer; Proc: ProcPtr);

FUNCTION  Call4(PacketPtr:PTR; Protocol:Integer; PacketLength:Integer;
		FirstHop: LongInt; SendProc: ProcPtr):Integer;
PROCEDURE Call4A(PacketPtr: PTR; PacketLength: Integer; HostAddr: LongInt;
	       FooData: PTR; Handler: ProcPtr);

IMPLEMENTATION

FUNCTION  CallB(Proc:ProcPtr):Boolean; EXTERNAL;
PROCEDURE Call1(ArgPtr:PTR; Proc: ProcPtr); EXTERNAL;
PROCEDURE Call1B(Flag: Integer; Proc: ProcPtr); EXTERNAL;
PROCEDURE Call1C(c:char; Proc: ProcPtr); EXTERNAL;
PROCEDURE Call(Proc: ProcPtr); EXTERNAL;
PROCEDURE Call3(PacketPtr:PTR; PacketLength: Integer; SourceAdd: LongInt;
		Handler: ProcPtr); EXTERNAL;
FUNCTION  Call3A(host: LongInt; file_name: StringPtr;
		 TransferDirection: Integer; tfs_alert: ProcPtr): Integer;
		 EXTERNAL;
PROCEDURE Call3B;EXTERNAL;
FUNCTION  Call4(PacketPtr:PTR; Protocol:Integer; PacketLength:Integer;
		FirstHop: LongInt; SendProc: ProcPtr):Integer; EXTERNAL;
PROCEDURE Call4A(PacketPtr: PTR; PacketLength: Integer; HostAddr: LongInt;
	       FooData: PTR; Handler: ProcPtr); EXTERNAL;
END.
!E!O!F!
#
#
echo extracting net/cust_lib.text...
cat >net/cust_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
Unit Cust_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

{$L-}

INTERFACE

   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf;

TYPE
      CustRecord = RECORD
	    LocalIPAddr: LongInt;
	    GateWIPAddr: LongInt;
	    NameServer: LongInt;
	    TimeServer: LongInt;
	    UserName: STR255;
	    UseAB: Boolean;
	    DefHost:STR255
	    END;

       Ref_CustRecord = ^ CustRecord;

CONST
	CFileName = 'Customization Values';

PROCEDURE ReadCustom(CP: Ref_CustRecord);

IMPLEMENTATION

{$S InitSeg}

PROCEDURE ReadCustom(CP: Ref_CustRecord);
VAR OSStatus: OSErr;
    FRN: Integer;
    RLength: LongInt;
    NodeNumber,NetNumber:INTEGER;
BEGIN
  if GetNodeAddress(NodeNumber,NetNumber) <> noErr
	then NodeNumber := 0;
  CP^.LocalIPAddr:= $80020000 + NodeNumber;
  CP^.GateWIPAddr:= $80020040;
  CP^.NameServer := $80020040;
  CP^.TimeServer := $80020040;
  CP^.UserName := 'AppleMAC';
  CP^.DefHost := 'Unknown';
  CP^.UseAB := TRUE;

  { Try to read in old values }
  OSStatus := FSOpen(CFileName, {Current Vol} 0, FRN);
  IF OSStatus = noErr THEN BEGIN
     { File is there, read in the values }
     RLength := sizeof(CustRecord);
     { Get to the start of the file }
     OSStatus := SetFPos(FRN,fsFromStart,0);
     IF OSStatus = noErr
	 THEN OSStatus := FSRead(FRN,RLength,POINTER(ORD4(CP)));
     OSStatus := FSClose(FRN);
     IF CP^.UseAB then
	 begin
	 CP^.LocalIPAddr := BitAnd(CP^.LocalIPAddr,$FFFFFF00) + NodeNumber;
	 end;
     END;
END;

END.

!E!O!F!
#
#
echo extracting net/err_lib.text...
cat >net/err_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
Unit Err_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

INTERFACE

{$L-}

   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf;

CONST errId = 49;

VAR errWindow:WindowPtr;
    Msg:STR255;

PROCEDURE ErrInit;
PROCEDURE CantAlloc(where:StringPtr; what:StringPtr);
PROCEDURE CantConnect(where:StringPtr; what:StringPtr);
PROCEDURE NotResponding(where:StringPtr);
PROCEDURE FOpenErr(fileName:StringPtr; status:OSErr);
PROCEDURE FWriteErr(fileName:StringPtr; status:OSErr);
PROCEDURE FReadErr(fileName:StringPtr; status:OSErr);
PROCEDURE Error(what:StringPtr);
PROCEDURE Error2(what:StringPtr; arg:StringPtr);
PROCEDURE Error3(what:StringPtr; arg:StringPtr; final:StringPtr);
PROCEDURE ErrorOS(what:StringPtr;status:OSErr);
PROCEDURE Fatal(what:StringPtr; re_boot:Boolean);

PROCEDURE Message(from:StringPtr;what:StringPtr);
PROCEDURE MsgRegister(wp:DialogPtr);
FUNCTION ClickResolve(ev:EventRecord):Boolean;
FUNCTION ActResolve(ev:EventRecord):Boolean;

FUNCTION StrCvt(S:Str255): StringPtr;
PROCEDURE reboot;

IMPLEMENTATION

FUNCTION StrCvt(S:Str255): StringPtr; EXTERNAL;
PROCEDURE reboot; external;

TYPE MsgRecPtr = ^MsgRec;

     MsgRec = RECORD
     next:MsgRecPtr;
     qType:QTypes;
     win:WindowPtr;
     END;

     MsgQ = RECORD
     qFlags:INTEGER;
     qHead:MsgRecPtr;
     qTail:MsgRecPtr
     end;

VAR theMsgQ:MsgQ;

{$S InitSeg}

PROCEDURE ErrInit;
BEGIN
     errWindow := POINTER(ORD4(GetNewDialog(errId,NIL,NIL)));
     theMsgQ.qFlags := 0;
     theMsgQ.qHead := NIL;
     theMsgQ.qTail := NIL;
END;

{$S	    }

FUNCTION ZapIt(wp:WindowPtr):Boolean;
VAR Dummy:OSErr;
    qp:MsgRecPtr;
BEGIN
     qp := theMsgQ.qHead;
     while (qp <> NIL) & (qp^.win <> wp) do
	  begin
	  qp := qp^.next;
	  end;
     if qp <> NIL { found it } then
	  begin
	  Dummy := dequeue(POINTER(ORD4(qp)),@theMsgQ);
	  DisposDialog(wp);
	  DisposPtr(POINTER(ORD4(qp)));
	  ZapIt := true;
	  end
     else
	  begin
	  ZapIt := false;
	  end;
END;

FUNCTION ClickResolve(ev:EventRecord):Boolean;
VAR wp:WindowPtr;
    code:INTEGER;
BEGIN
     code := FindWindow(ev.where,wp);
     if wp = errWindow then
	  begin
	  HideWindow(errWindow);
	  ClickResolve := true;
	  end
     else
	  ClickResolve := ZapIt(wp);
end;

FUNCTION ActResolve(ev:EventRecord):Boolean;
VAR wp:WindowPtr;
    code:INTEGER;
    fake:INTEGER;
    iHndl:Handle;
    iRect:Rect;
begin
     wp := POINTER(ORD4(ev.message));
     if wp = errWindow then
	    ActResolve := true
     else if BitAND(ev.modifiers,1) = 1 then
	  ActResolve := true
     else
	  ActResolve := ZapIt(wp);
end;

PROCEDURE MsgRegister(wp:WindowPtr);
VAR qp:MsgRecPtr;
BEGIN
     qp := POINTER(ORD4(NewPtr(sizeof(MsgRec))));
     qp^.next := NIL;
     qp^.qType := dummyType;
     qp^.win := wp;
     enqueue(POINTER(ORD4(qp)),@theMsgQ);
END;

PROCEDURE DoErr;
BEGIN
     ShowWindow(errWindow);
     SelectWindow(errWindow);
END;

PROCEDURE Message(from:StringPtr;what:StringPtr);
VAR  iType:INTEGER;
     iHndl:Handle;
     iRect:Rect;
BEGIN
     GetDItem(errWindow,1,iType,iHndl,iRect);
     SetIText(iHndl,from^);
     GetDItem(errWindow,2,iType,iHndl,iRect);
     SetIText(iHndl,what^);
     DoErr;
END;

PROCEDURE Error(what:StringPtr);
VAR  iType:INTEGER;
     iHndl:Handle;
     iRect:Rect;
BEGIN
     GetDItem(errWindow,1,iType,iHndl,iRect);
     SetIText(iHndl,'A network error happened.');
     GetDItem(errWindow,2,iType,iHndl,iRect);
     SetIText(iHndl,what^);
     SysBeep(2);
     DoErr;
END;

PROCEDURE Error2(what:StringPtr; arg:StringPtr);
VAR  iType:INTEGER;
     iHndl:Handle;
     iRect:Rect;
BEGIN
     GetDItem(errWindow,1,iType,iHndl,iRect);
     SetIText(iHndl,'A network error happened.');
     GetDItem(errWindow,2,iType,iHndl,iRect);
     SetIText(iHndl,concat(what^,arg^));
     SysBeep(2);
     DoErr;
END;

PROCEDURE Error3(what:StringPtr; arg:StringPtr; final:StringPtr);
VAR  iType:INTEGER;
     iHndl:Handle;
     iRect:Rect;
BEGIN
     GetDItem(errWindow,1,iType,iHndl,iRect);
     SetIText(iHndl,'A network error happened.');
     GetDItem(errWindow,2,iType,iHndl,iRect);
     SetIText(iHndl,concat(what^,arg^,final^));
     SysBeep(2);
     DoErr;
END;

PROCEDURE ErrorOS(what:StringPtr;status:OSErr);
VAR errstr:STR255;
BEGIN
     case status of
     bdNamErr:	    errstr := 'The file name is invalid.';
     dupFNErr:	    errstr := 'The file already exists.';
     dirFulErr:	    errstr := 'The directory is full.';
     dskFulErr:	    errstr := 'The disk is full.';
     eofErr:	    errstr := 'End of file.';
     extFSErr:	    errstr := 'External file system error.';
     fBsyErr:	    errstr := 'The file is busy.';
     fLckdErr:	    errstr := 'The file is locked.';
     fnfErr:	    errstr := 'The file does not exist.';
     fsRnErr:	    errstr := 'Difficulty with renaming.';
     ioErr:	    errstr := 'Disk I/O error.';
     mFulErr:	    errstr := 'Memory is full.';
     nsvErr:	    errstr := 'There is no such volume.';
     opWrErr:	    errstr := 'The file is already open for writing.';
     posErr:	    errstr := 'The given file position is invalid.';
     tmfoErr:	    errstr := 'Too many files are open now.';
     vLckdErr:	    errstr := 'Software volume lock.';
     wrPermErr:	    errstr := 'Permission does not allow writing.';
     wPrErr:	    errstr := 'Hardware volume lock.';
{$IFC DEBUG}
     fnOpnErr:	    errstr := 'File not open!';
     paramErr:	    errstr := 'Parameter error!';
     rfNumErr:	    errstr := 'Bad reference number!';
{$ENDC}
     end;	    { of case }
     Error2(what,@errstr);
END;

PROCEDURE CantAlloc(where:StringPtr; what:StringPtr);
BEGIN
     Error3(where,StrCvt(': I can''t get a new '),what);
END;

PROCEDURE CantConnect(where:StringPtr; what:StringPtr);
BEGIN
     Error3(where,StrCvt(': I can''t open a connection, protocol '),what);
END;

PROCEDURE NotResponding(where:StringPtr);
BEGIN
     Error3(where,
	    StrCvt(': The foreign host is not responding.  '),
	    StrCvt('The operation will be aborted.'));
END;

PROCEDURE FOpenErr(fileName:StringPtr; status:OSErr);
BEGIN
     Msg := concat('I can''t open "',fileName^,'";  ');
     ErrorOS(@Msg,status);
END;

PROCEDURE FWriteErr(fileName:StringPtr; status:OSErr);
BEGIN
     Msg := concat('I can''t write to "',fileName^,'";  ');
     ErrorOS(@Msg,status);
END;

PROCEDURE FReadErr(fileName:StringPtr; status:OSErr);
BEGIN
     Msg := concat('I can''t read from "',fileName^,'";	 ');
     ErrorOS(@Msg,status);
END;

PROCEDURE Fatal(what:StringPtr; re_boot:Boolean);
CONST FatalAlert = 333;
VAR dummy:INTEGER;
BEGIN
     ParamText(what^,'','','');
     dummy := StopAlert(FatalAlert,NIL);
     if re_boot then reboot else halt;
END;

END.

!E!O!F!
#
#
echo extracting net/icmp_lib.text...
cat >net/icmp_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
UNIT ICMP_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

INTERFACE

{$L-}

   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-PackIntf	  } PackIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-err_lib	  } Err_Lib,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-calls	  } Call_Lib,
      {$U net-ip_lib	  } IP_Lib;

{$L+}

CONST

{ Define some ICMP messages }
	PGNOSND	        = 0     { Couldn't send pkt };
	PGTMO	        = 1     { timedout };
	PGBADDATA       = 2     { rcved bad data back };
	PGWAITING       = 3     { waiting for rcpt of packet };
	PGSUCCESS       = 4     { success };

PROCEDURE icmprcv(p: PACKET; len:Integer; host:IN_Name);
FUNCTION icmp_destun(host: in_name; ip: Ref_IP; Packet_type: Integer):Integer;
PROCEDURE IcmpInit;
PROCEDURE inroute(host: in_name; hop1: Ref_in_name);

PROCEDURE ggprcv(p:PACKET; len:Integer; host:in_name);
PROCEDURE GgpInit;

IMPLEMENTATION

TYPE
    Ref_Ping = ^ ping;

    ping = PACKED RECORD    { ICMP Echo request/reply header }
		ptype: byte;
		pcode: byte;
		pchksum: Integer;
		pid: Integer;
		pseq: Integer;
		END;

CONST
	ICMPPROT= 1	        { ICMP Protocol number };
	ECHOREP = 0	        { ICMP Echo reply };
	DESTIN  = 3	        { Destination Unreachable };
	SOURCEQ = 4	        { Source quench };
	REDIR   = 5	        { Redirect };
	ECHOREQ = 8	        { ICMP Echo request };
	TIMEX   = 11	        { Time exceeded };
	PARAM   = 12	        { Parameter problem };
	TIMEREQ = 13	        { Timestamp request };
	TIMEREP = 14;
	INFO    = 15	        { Information request };
	ICMPSIZE = sizeof(ping);
	ECHOTMO	 = 6	        { Echo reply timeout period. };

	REDIRTABLEN =   16;     { Size + 1 of redirection table }

TYPE
	Raw_Bytes = PACKED ARRAY[0..32767] OF BYTE;

	Raw_Ptr_Type = ^Raw_Bytes;

	{ redirect table definitions }

	redent = RECORD
	       rd_dest: in_name;
	       rd_to: in_name;
	       END;

{ structure of an icmp destination unreachable packet }

	 destun = PACKED RECORD
		dtype: byte;			        { + 0 }
		dcode: byte;			        { + 1 }
		dchksum: Integer;		        { + 2 }
		dno1: Integer;			        { + 4 }
		dno2: Integer;			        { + 6 }
		dip: ip; { The header for the packet }  { + 8 }
		ddata: packed array [0..7] of byte;
	END;

{ structure of a timestamp reply }

	 tstamp = PACKED RECORD
		ttype: byte;
		tcode: byte;
		txsum: Integer;
		tid: Integer;
		tseq: Integer;
		tstampvals: packed array [0..2] of LongInt;
	END;

{ structure of an icmp redirect }

	 redirect = PACKED RECORD
		rdtype: byte;
		rdcode: byte;
		rdchksum: Integer;
		rdgw: in_name;
		rdip: ip;
		rddata: packed array [0..7] of byte;
	END;

VAR
	redtab: array [0..REDIRTABLEN-1] of redent;
	rednext : integer; { See initialization in IcmpInit = 0 }

	icmp: IPCONN;
	pingstate: Integer;
	requested: Ref_Task;
	sent: PACKET;
	snt_len: Integer;
	pingseq: Integer;

{$S InitSeg }

PROCEDURE IcmpInit;
VAR
	i: Integer;
BEGIN
	{ Once only initialization that used to be part of var decl's }
	FOR i := 0 TO REDIRTABLEN-1 DO BEGIN
	    redtab[i].rd_dest := 0;
	    redtab[i].rd_to := 0;
	    END;
	rednext := 0;
	pingstate := PGWAITING;
	requested := NIL;
	pingseq := 1;

	sent := NIL;
	snt_len := 0;

	icmp := in_open(ICMPPROT, @icmprcv);

	if (icmp = NIL) then
		CantConnect(StrCvt('ICMP'),StrCvt('IP'))
{$IFC DEBUG}
	WriteLn('ICMP: Opened ip conn.');
{$ENDC}
END; { End of IcmpInit }

{$S	    }

PROCEDURE wake_req;
BEGIN
	if (requested<>NIL) THEN tk_wake(requested);
END;

{ ICMP packet handler }

{$SETC DODESTIN := true}
{$SETC DOTIMEREQ := true}

{$SETC DOECHO := false}
{$SETC DOSOURCEQ := false}
{$SETC DOPARAM := false}
{$SETC DOTIMEX := false}
{$SETC DOINFO := false}

PROCEDURE icmprcv(p: PACKET; len:Integer; host:IN_Name);
VAR
	pip: Ref_IP;
	e: Ref_Ping;
	rd: ^redirect;
	pdp: ^destun;
	osum, xsum: Integer;
	data1, data2: PTR;
	i: integer;
	Raw_Ptr: Raw_Ptr_Type;
	DataMisMatch: Boolean;

BEGIN

{$IFC DEBUG}
	WriteLn('ICMP: p[',len,'] from ');
	out_inaddr(host);
	WriteLn('');
{$ENDC}

	pip := in_head(p);
	e := Ref_Ping(ORD4(in_data(pip)));

	osum := e^.pchksum;
	e^.pchksum := 0;

	if Odd(len) THEN BEGIN
		Raw_Ptr := POINTER(ORD4(e));
		Raw_Ptr^[len] := 0;
		END;

	xsum := BitNOT(cksum(POINTER(ORD4(e)),BitSR(len+1,1)));
	if (xsum <> osum) THEN BEGIN
		e^.pchksum := osum;
{$IFC DEBUG}
		WriteLn('ICMP: Bad xsum ',osum,' should have been ',xsum);
		in_dump(p);
{$ENDC}
		in_free(p);
		exit(icmprcv);
		END;

	e^.pchksum := osum;

	CASE (e^.ptype) OF
{$IFC DOECHO}
	    ECHOREQ: BEGIN
{$IFC DEBUG}
		Write('ICMP: Sending Echo Reply to ');
		out_inaddr(host);
		WriteLn('.');
{$ENDC}
		e^.ptype := ECHOREP;
		e^.pchksum := 0;
		if Odd(len) THEN BEGIN
			Raw_Ptr := POINTER(ORD4(e));
			Raw_Ptr^[len] := 0;
			END;

		e^.pchksum := BitNOT(cksum(POINTER(ORD4(e)),
						BitSR(len+1,1)));
		pip^.ip_src := pip^.ip_dest;
		pip^.ip_dest := host;
		if (in_write(icmp, p, len, host) <= 0) THEN BEGIN
{$IFC DEBUG}
			WriteLn('ICMP: Echo reply failed.\n');
{$ENDC}
		END;
		in_free(p);
		END; { end of ECHOREQ }

	ECHOREP: BEGIN
{$IFC DEBUG}
		WriteLn('ICMP: rcvd echo reply.');
{$ENDC}
		if(e^.pseq <> (pingseq-1)) THEN BEGIN
{$IFC DEBUG}
		    WriteLn('ICMP_RCV: Bad echo reply seq #');
{$ENDC}
		    in_free(p);
		    exit(icmprcv);
		    END;

		data1 := POINTER(ORD4(in_data(in_head(p)))+ICMPSIZE);
		data2 := POINTER(ORD4(in_data(in_head(sent)))+ICMPSIZE);

		FOR i := 0 to snt_len - 1 DO BEGIN
			DataMisMatch := (Data1^ <> Data2^);
			Data1 := POINTER(ORD4(Data1)+1);
			Data2 := POINTER(ORD4(Data2)+1);
			{ if(*data1++ != *data2++) }
			IF DataMisMatch THEN BEGIN
{$IFC DEBUG}
				WriteLn('bad icmp data at byte ',i);
				WriteLn('ICMP: Bad data in echo rep sent:');
				in_dump(sent);
				WriteLn('rcvd:');
				in_dump(p);
{$ENDC}
				pingstate := PGBADDATA;
				wake_req;
				in_free(p);
				exit(icmprcv);
				END; { End of outer IF test }
			END; { End of FOR loop }

		pingstate := PGSUCCESS;
		wake_req;
		in_free(p);
{$IFC DEBUG}
		WriteLn('ICMP: rcvd ICMP Echo Reply.');
{$ENDC}
		END; { End of ECHOREP }

{$ENDC}

{$IFC DODESTIN}
	DESTIN: BEGIN
		pdp := {(struct destun *)} POINTER(ORD4(in_data(in_head(p))));
{$IFC DEBUG}
		Write('ICMP: rcvd destination unreachable of type ',
		      pdp^.dcode,' on host ');
		out_inaddr(pdp^.dip.ip_dest);
		WriteLn('.');
{$ENDC}
		{ New from Tim -- cause the next attempt to route to this site to
		  fail.	 This is done by putting in a null redirect table entry. }
		FOR i := 0 TO REDIRTABLEN - 1 DO
			if (redtab[i].rd_dest = pdp^.dip.ip_dest) THEN BEGIN
				redtab[i].rd_to := 0;
				in_free(p);
				exit(icmprcv);
				END;

		redtab[rednext].rd_dest := pdp^.dip.ip_dest;
		redtab[rednext].rd_to := 0;
		IF rednext = REDIRTABLEN - 1
			THEN rednext := 0
			ELSE rednext := rednext + 1;
		in_free(p);
		END; {End of DESTIN }
{$ENDC}

	 REDIR: BEGIN
{$IFC DEBUG}
		WriteLn('ICMP: rcvd redirect.');
{$ENDC}
		rd := { (struct redirect *)} POINTER(ORD4(e));
{$IFC DEBUG}
		Write('redirect for ');
		out_inaddr(rd^.rdip.ip_dest);
		Write(' to ');
		out_inaddr(rd^.rdgw);
		WriteLn('.');
{$ENDC}
		FOR i := 0 TO REDIRTABLEN - 1 DO
			if (redtab[i].rd_dest = rd^.rdip.ip_dest) THEN BEGIN
				redtab[i].rd_to := rd^.rdgw;
				in_free(p);
				exit(icmprcv);
				END;

		redtab[rednext].rd_dest := rd^.rdip.ip_dest;
		redtab[rednext].rd_to := rd^.rdgw;
		IF rednext = REDIRTABLEN - 1
			THEN rednext := 0
			ELSE rednext := rednext + 1;
		in_free(p);

		END; { End of REDIR }

{$IFC DOSOURCEQ}
	SOURCEQ: BEGIN
{$IFC DEBUG}
		WriteLn('ICMP: rcvd source quench; ignoring.');
{$ENDC}
		in_free(p);
		END; {End of SOURCEQ }
{$ENDC}

{$IFC DOTIMEX}
	 TIMEX: BEGIN
{$IFC DEBUG}
		Write('ICMP: rcvd time exceeded message.');
{$ENDC}
		in_free(p);
		END; { End of TIMEX }
{$ENDC}

{$IFC DOPARAM}
	 PARAM: BEGIN
{$IFC DEBUG}
		WriteLn('ICMP: rcvd parameter problem message.');
{$ENDC}
		in_free(p);
		END; {End of PARAM }
{$ENDC}

{$IFC DOTIMEREQ}
	 TIMEREQ: BEGIN
{$IFC DEBUG}
		WriteLn('ICMP: rcvd Timestamp Request; ignoring.');
{$ENDC}
		e^.ptype := TIMEREP;
		e^.pchksum := 0;
		e^.pchksum := BitNOT(cksum(POINTER(ORD4(e)),
					BitSR(sizeof(tstamp),1)));
		pip^.ip_src := pip^.ip_dest;
		pip^.ip_dest := host;
		if (in_write(icmp, p, sizeof(tstamp), host) <= 0) THEN
		    BEGIN
{$IFC DEBUG}
		    WriteLn('ICMP: Couldn''t send timestamp reply.');
{$ENDC}
		    END;
		in_free(p);
		END; {End of TIMEREQ }
{$ENDC}

{$IFC DOINFO}
	 INFO:  BEGIN
{$IFC DEBUG}
		WriteLn('ICMP: rcvd information request.');
{$ENDC}
		in_free(p);
		END; {End of INFO }
{$ENDC}

	OTHERWISE BEGIN
{$IFC DEBUG}
		WriteLn('ICMP: rcvd unhandled packet of type ',e^.ptype;
		in_dump(p);
{$ENDC}
		in_free(p);
		END; {of otherwise }

		END; { OF CASE }
END; { End of Icmprcv }

{ ICMP Echo Request - returns 1 if host replies, 0 if timeout or error }

PROCEDURE pingtmo(Dummy:Ptr);
BEGIN
	pingstate := PGTMO;
	if (requested<>NIL) THEN tk_wake(requested);
END;

FUNCTION IcEchoRequest(host: in_name; length: Integer): Integer;
VAR
	p: PACKET;
	e: Ref_Ping;
	data: PTR;
	tm: Ref_Timer;
	i: integer;
	DummyBoolean: Boolean;

BEGIN
	IcEchoRequest := 0;
	p := in_alloc(40, 0);
	if (p = NIL) THEN BEGIN
		CantAlloc(StrCvt('ICMP'),StrCvt('packet'));
		IcEchoRequest :=  PGNOSND;
		exit(IcEchoRequest);
		END;

	e := Ref_Ping(ORD4(in_data(in_head(p)) ));
	e^.ptype := ECHOREQ;
	e^.pcode := 0;
	e^.pid :=   0;
	e^.pseq := pingseq;
	pingseq := pingseq + 1;

	{ Put 256 random numbers in the packet. }
	data := POINTER(ORD4(in_data(in_head(p))) + ICMPSIZE);
	FOR i := 0 TO Length - 1 DO BEGIN
	    Data^ := Random;
	    Data := POINTER(ORD4(Data) + 1);
	    END;

	{ Calculate the checksum }
	e^.pchksum:= 0;
	if Odd(ICMPSIZE+length) THEN Data^ := 0;

	e^.pchksum := BitNOT(cksum(POINTER(ORD4(e)),
					BitSR(ICMPSIZE+length+1,1) ));

	pingstate := PGWAITING;
	requested := tk_cur;
	sent := p;
	snt_len := length;
	tm := tm_alloc;
	if (tm = NIL) THEN BEGIN
		CantAlloc(StrCvt('ICMP'),StrCvt('timer'));
		IcEchoRequest :=  PGNOSND;
		exit(IcEchoRequest);
		END;

	tm_set(ECHOTMO, @pingtmo, NIL, tm);

	if (in_write(icmp, p, ICMPSIZE+length, host) <= 0) THEN BEGIN
{$IFC DEBUG}
		WriteLn('ICMP: Couldn''t send echo request.');
{$ENDC}
		DummyBoolean := tm_clear(tm);
		DummyBoolean := tm_free(tm);
		in_free(p);
		IcEchoRequest := PGNOSND;
		exit(IcEchoRequest);
		END;
{$IFC DEBUG}

	in_dump(p);
{$ENDC}

	while (pingstate = PGWAITING) DO tk_block;
	DummyBoolean := tm_clear(tm);
	DummyBoolean := tm_free(tm);
	in_free(p);
	sent := NIL;
	requested := NIL { Where did this variable come from ? };
	IcEchoRequest := pingstate;
END; { End of IcEchoRequest }

FUNCTION icmp_destun(host: in_name; ip: Ref_IP; Packet_type: Integer):Integer;
VAR
	p: PACKET;
	d: ^destun;
	i:integer;
	Raw_Ptr1, Raw_Ptr2: Raw_Ptr_Type;

BEGIN
	icmp_destun := 0;
{$IFC DEBUG}
		Write('ICMP: sending ');
		CASE Packet_Type OF
			0: Write('net');
			1: Write('host');
			2: Write('protocol');
			3: Write('port');
			4: Write('fragmentation needed');
			5: Write('source route failed');
			OTHERWISE Write('unknown packet type');
		END; { of case }
		Write(' dest unreachable to ');
		out_inaddr(host);
		WriteLn('');
{$ENDC}

	p := in_alloc(512, 0);
	if (p = NIL) THEN BEGIN
		CantAlloc(StrCvt('ICMP_DESTUN'),StrCvt('packet'));
		icmp_destun := 0;
		exit(icmp_destun);
		END;

	d := { (struct destun *)} POINTER(ORD4(in_data(in_head(p))));

	d^.dtype := DESTIN;
	d^.dcode := Packet_type;
	FOR i := 0 TO (sizeof(ip)+8)-1 DO BEGIN
		Raw_Ptr1 := POINTER(ORD4(ip));
		Raw_Ptr2 := POINTER(ORD4(@D^) + 8); { Note: Magic 8 in DIP dcl}
		Raw_Ptr2^[i] := Raw_Ptr1^[i];
		END;

	d^.dchksum := 0;
	d^.dchksum := BitNOT(cksum(POINTER(ORD4(d)),
					BitSR(sizeof(destun),1)));

	i := in_write(icmp, p, sizeof(destun), host);
	if (i <= 0) THEN BEGIN
{$IFC DEBUG}
		WriteLn('ICMP: Couldn''t send dest unreachable');
		WriteLn('ICMP: in_write returns ',i);
{$ENDC}
		END;

	in_free(p);
END; { End of icmp_destun }

{ Route a packet.
	Takes an internet address as its argument and a pointer to an
	internet address. Fills in this address with the internet address
	of the machine on our net which this packet should be sent to.

	Two routing algorithms are implemented: the MIT subnet routing
	algorithm, and a net-routing algorithm. The former places
	this interpretation on internet addresses: net,subnet,rsvd,host.
	It says: if the net and subnet of the destination of this packet
	and of my address are the same, the destination is on my network
	and I can send it directly to him. Otherwise, use the default
	gateway. The latter only checks on the net number.

	ICMP manages a routing table based on redirects which this
	code uses.
}

{$SETC CMU := false}

FUNCTION samenet(hi1: in_name; hi2:in_name): Boolean;
{ define some masks for testing addresses }
CONST
	 AMASK	 = $80;
	 AADDR	 = $00;
	 BMASK	 = $C0;
	 BADDR	 = $80;
	 CMASK	 = $E0;
	 CADDR	 = $C0;
VAR
	h1,h2: _ipname;
BEGIN

	h1.in_lname := hi1;
	h2.in_lname := hi2;

{$IFC CMU}
	samenet := ((h1.in_lst.in_net = h2.in_lst.in_net) AND
				  (h1.in_lst.in_nets = h2.in_lst.in_nets));
	exit(samenet);
{$ELSEC}
	if (BitAND(h1.in_lst.in_net,AMASK) = AADDR) THEN BEGIN
		{ We have a class A network }
		{ IF custom.c_route <> 0 THEN
		    samenet := (h1.in_lst.in_net = h2.in_lst.in_net)
		ELSE } samenet := ((h1.in_lst.in_net = h2.in_lst.in_net) AND
				  (h1.in_lst.in_nets = h2.in_lst.in_nets));
		exit(samenet);
		END;

	if (BitAND(h1.in_lst.in_net,BMASK) = BADDR) THEN BEGIN
		{ We have a class B network }
		{ IF custom.c_route <> 0 THEN
			samenet := (h1.in_lst.in_net=h2.in_lst.in_net) AND
				  (h1.in_lst.in_nets = h2.in_lst.in_nets)
		ELSE } samenet := (
				  (h1.in_lst.in_net = h2.in_lst.in_net) AND
				  (h1.in_lst.in_nets = h2.in_lst.in_nets) AND
				  (h1.in_lst.in_netss = h2.in_lst.in_netss));
		exit(samenet);
		END;

	if(BitAND(h1.in_lst.in_net,CMASK) = CADDR) THEN BEGIN
		{ Got a class C network }
		samenet := ( (h1.in_lst.in_net = h2.in_lst.in_net) AND
		       (h1.in_lst.in_nets = h2.in_lst.in_nets) AND
		       (h1.in_lst.in_netss = h2.in_lst.in_netss));
		exit(samenet);
		END;
{$ENDC}

{$IFC DEBUG}
	Write('bad address - ');
	out_inaddr(hi1);
	WriteLn('');
{$ENDC}
	samenet := false;
END; { End of samenet }

PROCEDURE inroute(host: in_name; hop1: Ref_in_name);
VAR i: integer;
BEGIN
{$IFC DEBUG}
	Write('Making a routing through the network for host ');
	out_inaddr(host); WriteLn('');
{$ENDC}

	{ first check through the redirect table for this host }
	FOR i := 0 TO REDIRTABLEN - 1 DO
	    IF redtab[i].rd_dest = 0 THEN cycle
	    ELSE if (redtab[i].rd_dest = host) THEN BEGIN
			hop1^ := redtab[i].rd_to;
			{ destination unreachable messages cause a null entry
			  to be inserted.  This makes IP think it can't write
			  to that host.	 However, these null entries are only
			  good until the next attempt to connect.  Eventually,
			  they should have a time value and a limited lifespan.
			  -- Tim }
			if redtab[i].rd_to = 0 then redtab[i].rd_dest := 0;
			exit(inroute);
			END;

	{ Check if it is on my net }
	if (samenet(LocalIPAddr, host)) THEN BEGIN
		hop1^ := host;
		exit(inroute);
		END;

	{ The host isn't on a net I'm on, so send it to the default gateway
		on IP }

	hop1^ := GWIPAddress;
END; { end of inroute}

{ A tiny GGP which will respond to ggp echo requests.

  (c) 1983 Massachussetts Institute of Technology
}

CONST
	GGP_Prot      = 3;

VAR
	ggp: IPCONN;

{$S InitSeg }

PROCEDURE GgpInit;
BEGIN

	ggp := in_open(GGP_Prot, @ggprcv);
	if ggp = NIL
	    THEN CantConnect(StrCvt('GGP'),StrCvt('InterNet'))
{$IFC DEBUG}
	else if	 BCBitAnd(NDEBUG,INFOMSG) THEN
		WriteLn('GGP: Opened InterNet connection.');
{$ENDC}
END;

{$S	    }

TYPE  ggping = PACKED RECORD
	 gtype: byte;
	 gcode: byte;
	 gseq: integer;
	END;

{ GGP packet handler }

PROCEDURE ggprcv(p:PACKET; len:Integer; host:in_name);
VAR
	pip: Ref_IP;
	e: ^ggping;

BEGIN

{$IFC DEBUG}
	if  BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN
		Write('GGP: Received packet from host ');
		out_inaddr(host);
		WriteLn('.');
		END;
{$ENDC}

	pip := in_head(p);
	e := { (struct ggping *)} POINTER(ORD4(in_data(pip)));

	if (e^.gtype = ECHOREQ) THEN BEGIN
		e^.gtype := ECHOREP;
		if (in_write(ggp, p, len, host) <= 0) THEN BEGIN
{$IFC DEBUG}
			if BCBitAnd(NDEBUG,BitOR(INFOMSG,PROTERR)) THEN
				WriteLn('GGP: Echo reply failed.');
{$ENDC}
			END
{$IFC DEBUG}
		else if BCBitAnd(NDEBUG,INFOMSG) THEN
			WriteLn('GGP: Sent echo reply.');
{$ENDC}
		END

{$IFC DEBUG}
	else if BCBitAnd(NDEBUG,BitOR(PROTERR,INFOMSG)) THEN BEGIN
	  WriteLn('GGP: Received unhandled packet type',e^.gtype);
	  END
{$ENDC}
	;
	in_free(p);
END; { End of ggprcv }

END.
!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting net/ip_lib.text...
cat >net/ip_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
UNIT IP_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

INTERFACE

{$L-}

   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-PackIntf	  } PackIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-err_lib	  } Err_Lib,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-cust_lib	  } Cust_Lib,
      {$U net-calls	  } Call_Lib;

{$L+}

CONST
    AB_IP_Socket = 72; { Just some socket that everyone agrees on }

TYPE

{ The buffer organization is somewhat intertwined with the queue organization.
	Each buffer is in a queue, either the free queue or the used queue
	(or the buffer is currently being used by a user program or the
	interrupt level routines, in which case it does not appear in a
	queue). When a buffer is in a queue, it has a pointer to the next
	buffer in the queue. If there is no next buffer, its pointer points
	at nullbuf. Also, each buffer knows its own length. }

    Packet = ^ net_buf;

    net_buf = PACKED RECORD
	nb_elt: Packet;	        { queue link }
	nb_type: QTypes;        { for Mac queues }
	nb_buff: PTR;	        { The buffer }
	nb_tstamp: LongInt;     { packet timestamp }
	nb_len: Integer;        { Length of buffer }
	END;

{ Internet connection information }

    ip_iob =  RECORD
	c_prot: byte;		     { protocol }
	c_handle: ProcPtr;      { int returning procedure - see below: }
	END;

    IPCONN = ^ip_iob;	        { The IP connection type }

    in_name = LongInt;

    Ref_in_name = ^ in_name;

    ip = PACKED RECORD
	ip_ver: 0..15;	        { Header version }
	ip_ihl: 0..15;	        { Internet header length in 32 bit words }
	ip_tsrv: byte;	        { Type of service }
	ip_len: integer;        { Total packet length including header }
	ip_id: integer;	        { ID for fragmentation }
	ip_flgs: 0..7;	        { flags }
	ip_foff: 0..8191;       { Fragment offset }
	ip_time: byte;	        { Time to live (secs) }
	ip_prot: byte;	        { protocol }
	ip_chksum: integer;     { Header checksum }
	ip_src: in_name;        { Source name }
	ip_dest: in_name;       { Destination name }
	END;

    Ref_IP = ^ IP;

{ IP header and internet names }

      in_lst_type = PACKED RECORD
		in_net: byte;
		in_nets: byte;
		in_netss: byte;
		in_host: byte;
		END;

       _ipname = PACKED RECORD CASE Integer OF
	       0: (in_lname: in_name);
	       1: (in_lst: in_lst_type);
	       END;

CONST
    AB_IP = 22;	   { Internet Protocol - unofficial number for debugging     }

	IP_PROTOCOL = 0;
	CHAOS	    = 1;
	PUP	    = 2;
	SLP	    = 3;
	ADR	    = 4;    { Dave Plummer's Address Resolution Protocol }

    IPHSIZ  = sizeof(ip)    { internet header size };
    INETLEN =	    576	    { maximum size of internet packet (bytes) };

	DSTNET	        = 0;
	DSTHOST	        = 1;
	DSTPROT	        = 2;
	DSTPORT	        = 3;
	DSTFRAG	        = 4;
	DSTSRC	        = 5;

VAR
	LocalIPAddress, GWIPAddress: in_name;
	DefaultHost:STR255;
	Use_AB: Boolean;
	ip_queue:QHdr;
	IPStack:INTEGER;

CONST
	Num_Name_Servers =      1;      { Number of name servers for which
					  we have addresses }
	Last_NS_Index	 =      0;      { = Num_Name_Servers - 1 }

VAR
	NSIPAddress: ARRAY [0..Last_NS_Index] OF in_name;

CONST
	Num_Time_Servers =      1;      { Number of time servers for which
					  we have addresses }
	Last_TS_Index	 =      0;      { = Num_Time_Servers - 1 }

VAR
	TSIPAddress: ARRAY [0..Last_TS_Index] OF in_name;

CONST
	Max_User_Name = 8;
VAR
	User_Name: STRING[Max_User_Name];

FUNCTION in_open(prot:byte; handler: ProcPtr):IPCONN;
FUNCTION in_alloc(datalen:Integer; optlen:Integer):PACKET;
FUNCTION in_write(conn: IPCONN; p:PACKET; datalen:Integer; fhost: in_name):INTEGER;
PROCEDURE in_free(p: PACKET);
FUNCTION in_more: Boolean;
PROCEDURE in_close;
FUNCTION inverify(p:Packet):Boolean;

PROCEDURE cvt_inaddr(fhost: in_name; VAR s:STR255);
PROCEDURE indemux(Fake:PTR);
PROCEDURE in_init;
FUNCTION in_data(pip:Ref_IP): PTR;
FUNCTION in_head(ppkt:Packet): Ref_IP;
FUNCTION CkSum(BufPtr:PTR; Count:Integer): Integer;
FUNCTION in_mymach(host: in_name): in_name;
PROCEDURE in_stats;

VAR
	freeq:QHdr;
	CurW_Hdl: ABRecHandle;

IMPLEMENTATION

{ Functions from ICMP_LIB }

FUNCTION icmp_destun(host: in_name; ip: Ref_IP; Packet_type: Integer):Integer;
	external;

PROCEDURE inroute(host: in_name; hop1: Ref_in_name); external;

CONST

{ Some useful definitions }

	Cur_IP_VER  = 4		        { internet version };
	IP_IHL_Off  = 5		        { IN header length in longwords };
	IP_TSRV = 0		        { default type of service };
	IP_ID   = 0		        { kernel fills in IN id };
	NO_IP_FLGS = 0		        { no fragmentation yet };
	Cur_IP_FOFF = 0		        { " " " };
	IP_TIME = 255		        { maximum time to live };
	FirstIPXSUM  = 0	        { initial checksum };
	FirstIPLEN = sizeof(ip)	        { internet header length };
	NBUFINIT        = 6;
	LBUFINIT        = 600;

{ The following goodly macros will have to be changed to functions }

FUNCTION in_head(ppkt:Packet): Ref_IP;
BEGIN
   in_head := {(struct ip *)} POINTER(ORD4( ppkt^.nb_buff ));
END;

FUNCTION in_data(pip:Ref_IP): PTR;
BEGIN
    { Note: This must be changed because of bit packing in the header }
    in_data := { (char *) } POINTER(ORD4(pip) + BitSL(pip^.ip_ihl,2));
END;

FUNCTION in_options(pip:Ref_IP): PTR;
BEGIN
    in_options := { (char *) } POINTER( ORD4(pip) + BitSL(IP_IHL_Off,2) );
END;

FUNCTION in_optlen(pip: Ref_IP):Byte;
BEGIN
  in_optlen := BitSL(pip^.ip_ihl - IP_IHL_Off,2);
END;

FUNCTION CkSum(BufPtr:PTR; Count:Integer): Integer; external;

{$IFC DEBUG}
{ This function dumps an internet packet to the screen. It uses some of the
	screen handling functions to help in this tedious chore. }

PROCEDURE in_dump(p:PACKET);
VAR
	i,j : Integer;
	pip: Ref_IP;
	data: PTR;
	xsum, osum: integer;
	out: STR255;
BEGIN
	pip := in_head(p);

	data := p^.nb_buff;

	WriteLn('Packet address = ',ORD4(p));
(*
	FOR i := 0 to 6 DO BEGIN
		WriteLong(ord4(data));Write(': ');
		FOR j := 0 to 23 DO BEGIN
			Num2HexStr(data^,out); out := copy(out,3,2);
			Write(out);
			data := POINTER(ORD4(Data) + 1); { Print a word at a time }
			END;
		WriteLn('');
		END;
*)
	{ Display header info in reasonable form }

	WriteLn('Header length = ',pip^.ip_ihl,
		', IP Length = ',{ bswap } (pip^.ip_len),
		', Total Length = ',p^.nb_len);

	WriteLn('Version = ',pip^.ip_ver,
		', Type of Ser = ',pip^.ip_tsrv,
		', tProtocol = ',pip^.ip_prot,
		', Time To Live = ',pip^.ip_time);

	WriteLn('Frag Offset = ',pip^.ip_foff,', Flags = ',pip^.ip_flgs);

	Write('Source = ');
	out_inaddr(pip^.ip_src);
	Write(', Destin = ');
	out_inaddr(pip^.ip_dest);
	WriteLn('');

	WriteLn('ID = ',pip^.ip_id);

	WriteLn('Checksum = ',pip^.ip_chksum);

	osum := pip^.ip_chksum;
	pip^.ip_chksum := 0;
	xsum := BitNOT(cksum(POINTER(ORD4(pip)), BitSL(pip^.ip_ihl,1)));
	WriteLn('Computed xsum = ',xsum);

	pip^.ip_chksum := osum;
	if (xsum = osum) THEN WriteLn('Checksum is CORRECT.')
			ELSE WriteLn('Checksum is NOT CORRECT.');

END; { End of in_dump }
{$ENDC}

VAR ipdemux:Ref_Task;   { demuxer task address }

{ internet statistics  - all initialized in in_init }
 ipdrop: integer;	        { ip packets dropped }
 ipxsum: integer;	        { ip packets with bad checksums }
 iplen : integer;	        { ip packets with bad lengths }
 ipdest: integer;	        { ip packets with bad destinations }
 ipttl : integer;	        { ip packets with time to live := 0 }
 ipprot: integer;	        { no server for protocol }
 ipver : integer;	        { bad ip version number }
 iprcv : integer;	        { number of ip packets received }
 ipfrag: integer;	        { number of fragments received }
 ipwwop: Integer;	        { Number of times awakened w/o packets }
 ipmulti: Integer;	        { Number of times found > 1 packet on queue }

{ Allocate and internet packet. Has to grunge around with local net header
	sizes to do the right thing. }

FUNCTION in_alloc(datalen:Integer; optlen:Integer):PACKET;
VAR
	p: PACKET;
	pip: Ref_IP;
	len: integer;
	status:OSErr;
BEGIN

	optlen := BitAND((optlen + 3),BitNOT(3));
{       len := (IPHSIZ + optlen + datalen + 1) & ~1;}
	len := (IPHSIZ + optlen + datalen + 1);
	IF Odd(len) THEN len := len - 1;

	if (datalen > INETLEN) THEN BEGIN
{$IFC DEBUG}
		WriteLn('IN_ALLOC: Packet size ',datalen,'is too large.');
{$ENDC}
		in_alloc := NIL;
		exit(in_alloc);
		END;

	p := POINTER(ORD4(freeq.qHead));
	if p <> NIL then begin
		status := dequeue(freeq.qHead,@freeq);
		if status <> noErr then p := NIL;
		end;
	if p = NIL THEN BEGIN
		CantAlloc(StrCvt('IN_ALLOC'),StrCvt('packet'));
		in_alloc := NIL;
		END
	else begin
		pip := in_head(p);
		pip^.ip_ihl := IP_IHL_Off + (optlen DIV 4);
		in_alloc := p;
		end;
END; {End of in_alloc}

{ Free up an internet packet }

PROCEDURE in_free(p: PACKET);
BEGIN
	enqueue(POINTER(ORD4(p)), @freeq);
END;

{ Intialize the internet layer. }

CONST
	LastIPConn = 9;
VAR
	ipsnd: integer;
	uid: integer;

	ipconns: array [0..LastIPConn] of ipconn;	       { demux table }
	nipconns: Integer; { current posn in demux table }

FUNCTION arp_init: Ref_Task; external;
PROCEDURE ip_listen; external;
PROCEDURE listeninit(it:Ref_Task; at:Ref_Task; q:QHdrPtr; fq:QHdrPtr); external;

{$S InitSeg }
PROCEDURE in_init;
VAR     i:INTEGER;
	temp_packet:PACKET;
	CR: CustRecord;
	TempSkt: Byte;
	status:OSErr;
	NBUF: LongInt;		   { # of packet buffers }
	divisor: LongInt;
	LBUF: Integer;		   { size of packet buffers }
BEGIN

{$IFC NOT DEBUG}
	 ReadCustom(@CR);
	 LocalIPAddress := CR.LocalIPAddr;
	 GWIPAddress := CR.GateWIPAddr;
	 NSIPAddress[0] := CR.NameServer;
	 TSIPAddress[0] := CR.TimeServer;
	 User_Name := CR.UserName;
	 Use_AB := CR.UseAB;
	 DefaultHost := CR.DefHost;
{$ENDC}

	nipconns := 0;		        { current posn in demux table }
	ipsnd := 0;
	uid := 1 ;

	{ internet statistics are initialized }
	ipdrop := 0;	        { ip packets dropped }
	ipxsum := 0;	        { ip packets with bad checksums }
	iplen := 0;	        { ip packets with bad lengths }
	ipdest := 0;	        { ip packets with bad destinations }
	ipttl := 0;	        { ip packets with time to live := 0 }
	ipprot := 0;	        { no server for protocol }
	ipver := 0;	        { bad ip version number }
	iprcv := 0;	        { number of ip packets received }
	ipfrag := 0;	        { number of fragments received }
	ipwwop := 0;
	ipmulti := 0;

	{ Create the queue of free packets. Format each packet and add it to
		the tail of the queue }

{$IFC DEBUG}
	WriteLn('About to allocate the packet pool');
{$ENDC}

	divisor := 32768;
	NBUF := ((ORD4(ApplicZone^.bkLim) - ORD4(ApplicZone)) div divisor)
		+ NBUFINIT;
	LBUF := LBUFINIT;	  { size of packet buffers }

	{ Initialize the receive queue }
	ip_queue.qFlags := 0;
	ip_queue.qHead := NIL;
	ip_queue.qTail := NIL;

	{ Initialize the free packet queue }
	freeq.qFlags := 0;
	freeq.qHead := NIL;
	freeq.qTail := NIL;

	FOR i := 0 TO NBUF - 1 DO BEGIN
		temp_packet := POINTER(ORD4(NewPtr(sizeof(Net_Buf))));
		if (temp_packet = NIL) THEN
			Fatal(StrCvt('in_init: can''t make free queue'),false);

		temp_packet^.nb_type := dummyType;
		temp_packet^.nb_tstamp := 0;
		temp_packet^.nb_len := 0;
		temp_packet^.nb_buff := POINTER(ORD4(NewPtr(LBUF)));

		if (temp_packet^.nb_buff = NIL) THEN
	     Fatal(StrCvt('Ran out of packet storage during in_init'),false);

		enqueue(POINTER(ORD4(temp_packet)), @freeq);
		END;

	{ DDP Send handle initialization }
	CurW_Hdl := POINTER(ORD4(NewHandle(ddpSize)));
	CurW_Hdl^^.abResult := noErr;	     { Another write can be done }

	ipdemux := tk_fork(tk_cur, @indemux, IPStack, 'IPDemux',NIL);
	listeninit(ipdemux,arp_init,@ip_queue,@freeq);

	{ Initialize the AB }
	TempSkt := AB_IP_Socket;
	IF DDPOpenSocket(TempSkt,@ip_listen) <> 0 THEN
	    CantConnect(StrCvt('DDPOpenSocket'),StrCvt('socket for Applebus IP'));
END; { end of in_init }

{$S	    }

{ Return true if there are any enqueued, unprocessed packets in system. }

FUNCTION in_more: Boolean;
BEGIN
	in_more := ip_queue.qHead <> NIL;
END;

{ Return the address of our machine relative to a certain foreign host. }

FUNCTION in_mymach(host: in_name): in_name;
BEGIN
	in_mymach := LocalIPAddr;
END; { End of my_mach }

{ Open a protocol connection on top of internet. Protocol information
	necessary for packet demultiplexing; handler is upcalled
	upon receipt of packet. handler is
		int handler(p, len, fhost)
			PACKET p
			int len
			in_name fhost
}

VAR IPConn_Table: ARRAY [0..LastIPConn] of ip_iob;

FUNCTION in_open (prot:byte; handler: ProcPtr):IPCONN;
VAR
	i: integer;
	conn: IPCONN;
BEGIN
	FOR i := 0 to nipconns-1 DO
		IF (ipconns[i]^.c_prot = prot) THEN BEGIN
			in_open := NIL;
			exit(in_open);
			END;

	IF nipconns = LastIPConn + 1 THEN conn := NIL
		ELSE conn := @IPConn_Table[nipconns];

	if (conn = NIL) THEN BEGIN
		in_open := NIL;
		exit(in_open);
		END;
	conn^.c_prot := prot;
	conn^.c_handle := handler;
	ipconns[nipconns] := conn;
	nipconns := nipconns + 1;
	in_open := conn;
END; { end of in_open }

{ Fill in the internet header in the packet p and send the packet through the
	appropriate net interface. This will involve using routing. Packets for
	a certain connection are all routed at connection open time, but some
	facility should be provided to allow for later rerouting. }

PROCEDURE IP2AB(IP: in_name; VAR AB:AddrBlock); external;

FUNCTION in_write(conn: IPCONN; p:PACKET; datalen:Integer; fhost: in_name):INTEGER;
CONST
    CantReachHost = -2;
VAR
	firsthop: in_name;
	pip: Ref_ip;
	len: integer;
	DestAddr: AddrBlock;
	err: OSerr;
BEGIN

	IF (datalen > LBUFINIT) THEN BEGIN
{$IFC DEBUG}
		WriteLn('IN_WRITE: Received a stupid packet length',datalen);
		{ if BCBitAnd(NDEBUG,BUGHALT) THEN HALT; }
{$ENDC}
		in_write := -1;
		exit(in_write);
		END;

{$IFC DEBUG}
	Write('IP sending packet of length ',datalen:1,
	      ' protocol ',conn^.c_prot,' to ');
	out_inaddr(fhost);
	WriteLn('.');
{$ENDC}

	{ perform routing. Have to route on each and every packet going
		out because have to find first hop. }
	inroute(fhost, @firsthop);
	if (firsthop = 0) THEN BEGIN
{$IFC DEBUG}
		Write('IN_WRITE: Couldn''t route packet to host ');
		out_inaddr(fhost);
		WriteLn('.');
{$ENDC}
		in_write := 0;
		exit(in_write);
		END;

{$IFC DEBUG}
	Write('IP packet routed to host ');
	out_inaddr(firsthop);
	WriteLn('.');
{$ENDC}

	pip := in_head(p);
	pip^.ip_ver := Cur_IP_VER;
	pip^.ip_time := IP_TIME;

	pip^.ip_flgs := NO_IP_FLGS;
	pip^.ip_foff := Cur_IP_FOFF;

	pip^.ip_id := { bswap } (uid);
	uid := uid + 1;

	pip^.ip_chksum := FirstIPXSUM;
	pip^.ip_src := LocalIPAddress;
	pip^.ip_dest := fhost;

	len := BitSL(pip^.ip_ihl,2) + datalen;

	pip^.ip_len := { bswap } (len);

	pip^.ip_tsrv := 0;
	pip^.ip_prot := conn^.c_prot;

	{ It's CHECKSUM time!! }
	pip^.ip_chksum := BitNOT(cksum(POINTER(ORD4(pip)), BitSL(pip^.ip_ihl,1)));
	ipsnd := ipsnd + 1;

{$IFC DEBUG}
	WriteLn('About to call the network to send');
{$ENDC}

	IP2AB(firsthop,DestAddr);

	IF DestAddr.ANode = 0 THEN BEGIN
		in_write := CantReachHost;
{$IFC DEBUG}
		WriteLn('in_write: Cannot reach host - IP2AB translation failed');
{$ENDC}
		exit(in_write);
		END;

{$IFC DEBUG}
	WriteLn('in_write to node ',DestAddr.ANode:1);
{$ENDC}
	{ Wait until previous write finished }
	WHILE CurW_Hdl^^.abResult = 1 DO tk_yield;
	{ Fill in the disk block }
	WITH CurW_Hdl^^ DO BEGIN
		ddpType := AB_IP;
		ddpSocket := AB_IP_Socket;
		ddpAddress := DestAddr;
		ddpReqCount := len;
		ddpDataPtr := p^.nb_buff;
		END;
	{ And do the write }
	err := DDPWrite(CurW_Hdl,false,true);
{$IFC DEBUG}
	WriteLn('in_write: exiting');
{$ENDC}
	if err = noErr then in_Write := 1 else in_write := -3;
END; { end of in_write }

FUNCTION inverify(p:Packet):Boolean;
VAR     pip:Ref_IP;
	csum: integer;	        { packet checksum }
BEGIN
	pip := in_head(p);

	IF (p^.nb_len < { bswap } (pip^.ip_len)) THEN BEGIN

{$IFC DEBUG}
		WriteLn('Packet length bad; dropped.' );
		{ in_dump(p); }
{$ENDC}
		iplen := iplen + 1;
		inverify := FALSE;
		exit(inverify);
		END;

	if (pip^.ip_ver <> Cur_IP_VER) THEN BEGIN
{$IFC DEBUG}
		WriteLn('Version number bad, packet dropped.');
		{ in_dump(p); }
{$ENDC}
		ipver := ipver + 1;
		inverify := FALSE;
		exit(inverify);
		END;

	csum := pip^.ip_chksum;
	pip^.ip_chksum := 0;
	if (csum <> BitNOT(cksum(POINTER(ORD4(pip)),BitSL(pip^.ip_ihl,1)))) THEN
		BEGIN
		pip^.ip_chksum := csum;
{$IFC DEBUG}
		WriteLn('Checksum bad, packet dropped.');
		{ in_dump(p); }
{$ENDC}
		ipxsum := ipxsum + 1;
		inverify := FALSE;
		exit(inverify);
		END;

	if(pip^.ip_dest <> LocalIPaddr) THEN BEGIN
{$IFC DEBUG}
		WriteLn('Received packet not for me.');
{$ENDC}
		inverify := FALSE;
		exit(inverify);
		END;

	if (pip^.ip_foff <> 0) OR Odd(pip^.ip_flgs) THEN BEGIN

{$IFC DEBUG}
		WriteLn('Fragment received; dropped.');
		{ in_dump(p); }
{$ENDC}
		ipfrag := ipfrag + 1;
		inverify := FALSE;
		exit(inverify);
		END;

	inverify := TRUE;
END;

{ This is the internet demultiplexing routine. It handles packets received by
	the per-net task, verifies their headers and does the upcall to
	the whoever should receive the packet. All the guts of demultiplexing
	is in this piece of code. If the packet doesn't belong to anyone,
	this gets logged and the packet dropped.        }

PROCEDURE indemux(Fake:PTR);
VAR
	pip: Ref_IP;	        { the internet header }
	conn: IPCONN;	        { an internet connection }
	i: Integer;
	Dummy: integer;	        { To hold value of ICMP_DESTUN }
	p: Packet;
	len:INTEGER;
	success:BOOLEAN;
BEGIN
    CheckTask;	    { Problem with stacks? }

    WHILE true DO BEGIN
	if ip_queue.qHead = NIL then tk_block; { Wait for something to come }

{$IFC DEBUG}
	WriteLn('Running: IP Demuxer');
{$ENDC}

	{ Get a packet }
	p := POINTER(ORD4(ip_queue.qHead));
	if p = NIL THEN BEGIN { Awakened w/o a packet! }
		ipwwop := ipwwop + 1;
{$IFC DEBUG}
		WriteLn('Awakened w/o any packets to process ');
{$ENDC}
		cycle;
		END;
	if dequeue(POINTER(ORD4(p)),@ip_queue) <> noErr then
		begin
{$IFC DEBUG}
		WriteLn('dequeue failure (p = ',ORD4(p),')');
{$ENDC}
		cycle;
		end;

{$IFC DEBUG}
	WriteLn('Processing a packet of length ',p^.nb_len);
{$ENDC}

	iprcv := iprcv + 1;

	if NOT inverify(p) THEN BEGIN
		ipdrop := ipdrop + 1;
		in_free(p);
		cycle;
		end;

	pip := in_head(p);
	len := { bswap } (pip^.ip_len);

	{ The packet is now verified; the header is correct. Now we have
		to demultiplex it among our internet connections. }

{$IFC DEBUG}
	WriteLn('IP: Received packet of length ',len-FirstIPLEN:1,
		' in protocol ',pip^.ip_prot:1);
{$ENDC}

	success := false;
	FOR i := 0 to nipconns - 1 DO BEGIN
		conn := ipconns[i];
		if (conn^.c_prot = pip^.ip_prot) THEN
			if (conn^.c_handle = NIL) THEN leave
			else BEGIN
{$IFC DEBUG}
			    WriteLn('handler found, delivering...');
{$ENDC}
			    CALL3(POINTER(ORD4(p)), len - FirstIPLEN, { ??? }
				  pip^.ip_src, conn^.c_handle);
			    CheckTask;
			    success := true;
{$IFC DEBUG}
			    WriteLn('finished executing IP handler');
{$ENDC}
			    tk_yield;
			    leave;
			    END;
		END;

	if not success then begin
		{ Didn't manage to demultiplex the packet.
		  We should drop it and go away. }
{$IFC DEBUG}
		WriteLn('Discarding pkt for unhandled protocol ',pip^.ip_prot:1);
{$ENDC}

		Dummy := icmp_destun(pip^.ip_src, pip, DSTPROT);
		ipprot := ipprot + 1;
		ipdrop := ipdrop + 1;
		in_free(p);
		end;
    END; { end of infinite while }
END; { End of indemux }

PROCEDURE in_close;
{ This procedure shuts down the IP connection }
VAR Status : Integer;
BEGIN
    Status := DDPCloseSocket(AB_IP_Socket);
END;

{ pretty print the statistics }

FUNCTION lfailures:INTEGER; external;

PROCEDURE in_stats;
VAR
	s:STR255;
	dp:DialogPtr;
	iType:INTEGER;
	itemHndl:Handle;
	box:Rect;
	i: Integer;
	tmpP:Packet;

    PROCEDURE SetIt(item:INTEGER; number:INTEGER);
    BEGIN
	NumToStr(number,s);
	GetDItem(dp,item,iType,itemHndl,box);
	SetIText(itemHndl,s);
    END;

BEGIN
	dp := GetNewDialog(77,NIL,POINTER(-1));
	SetIt(1,iprcv);
	SetIt(2,ipsnd);
	SetIt(3,ipdrop);
	SetIt(4,ipxsum);
	SetIt(5,ipprot);
	SetIt(6,ipver);
	SetIt(7,iplen);
	SetIt(8,ipttl);
	SetIt(9,ipfrag);
	tmpP := POINTER(ORD4(freeq.qHead));
	i := 0;
	while tmpP <> NIL do begin
		i := i+1;
		tmpP := tmpP^.nb_elt;
		end;
	SetIt(10,i);
	SetIt(11,lfailures);
{$IFC DEBUG}
	Write('packet addresses: ');
	tmpP := POINTER(ORD4(freeq.qHead));
	while tmpP <> NIL do begin
		Write(ORD4(tmpP),' ');
		tmpP := tmpP^.nb_elt;
		end;
	WriteLn('');
{$ENDC}
	MsgRegister(dp);
END; {End of in_stats}

{ CROCK routines. output an internet address in pretty octal form }

PROCEDURE cvt_inaddr(fhost: in_name; VAR s:STR255);
VAR     host:_ipname;
	s1,s2,s3,s4: STR255;
begin
	host.in_lname := fhost;
	NumToString(BitAND(host.in_lst.in_net,  255),s1);
	NumToString(BitAND(host.in_lst.in_nets, 255),s2);
	NumToString(BitAND(host.in_lst.in_netss,255),s3);
	NumToString(BitAND(host.in_lst.in_host, 255),s4);
	s := concat(s1,'.',s2,'.',s3,'.',s4);
end;

{$IFC DEBUG}
PROCEDURE out_inaddr(fhost: in_name);
VAR host:_ipname;
BEGIN

	host.in_lname := fhost;
	Write(BitAND(host.in_lst.in_net,255):1,'.',
	      BitAND(host.in_lst.in_nets,255):1,'.',
	      BitAND(host.in_lst.in_netss,255):1,'.',
	      BitAND(host.in_lst.in_host,255):1);

END; { End of out_inaddr }
{$ENDC}

END.

!E!O!F!
#
#
echo extracting net/ip_listen.text...
cat >net/ip_listen.text <<'!E!O!F!'
	.Title 'IP Socket Listener'
; code for Appletalk IP socket listener
; written by Tim Maroney, C-MU, July 1985
; modified 13 Sept 85 to eliminate use of A5 for global data (because of Switcher)
; please see the copyright notice in the file copyright/notice

	.nomacrolist
	.PAGE

.INCLUDE        -UPPER-TLASM-SYSEQU
.INCLUDE        -UPPER-TLASM-ATALKEQU
.INCLUDE        -UPPER-TLASM-TOOLEQU
.INCLUDE        -UPPER-TLASM-TOOLMACS

AB_IP	        .EQU    22      ; DDP protocol number for IP
AB_ARP	        .EQU    23      ; DDP protocol number for ARP
IPsize	        .EQU    600     ; length of an IP packet buffer
ARPSize	        .EQU    24      ; length of an ARP buffer
AB_IP_Socket    .EQU    72      ; static (sigh) IP socket number
nb_len	        .EQU    14      ; offset of the length field in a packet
nb_buff	        .EQU    6       ; offset of the buffer pointer in a packet
nb_tstamp       .EQU    10      ; offset of the time stamp field in a packet

; Here is the code for the socket listener.  See the Appletalk Manager
; documentation to understand this strange routine.

	.PROC   ip_listen
	.REF    ARPbuffer,IPEnable,arp_wake,ip_wake,ARPAllowed
	.REF    IPrdpkt,IPrdbuf,failcnt

; paranoia: make sure the packet is for our socket

	cmpi.b  #AB_IP_Socket,d0
	beq.s   @1
	bra     recvfail
@1

; First we have to find out the protocol, so we can figure out where
; to store the packet.  This requires figuring out whether this DDP
; header is long or short.

	lea     toRHA(a2),a3    ; a3 := addr of top of RHA (LAP header)
	cmpi.b  #shortDDP,lapType(a3)   ; short DDP header?
	; the addq may be used here beause an addq to an address register
	; leaves the condition codes untouched
	addq.l  #lapHdSz,a3     ; a3 := addr of DDP Header (RHA + 3)
	bne.s   @2	        ; if not, it's long
	move.b  sDDPType(a3),d0 ; d0 := ddp protocol byte from short hdr
	bra.s   @3
@2
	move.b  ddpType(a3),d0  ; d0 := ddp protocol byte from long hdr
@3

; d0 now contains the protocol ID from the DDP Header.

	cmpi.b  #AB_IP,d0       ; is this a packet for IP?
	bne.s   @4	        ; if not, maybe for ARP

; IP packet handling

	lea     IPrdbuf,a3
	tst.l   (a3)	        ; is there an ip buffer to read into?
	bne.s   @5	        ; if there is, continue
	clr.w   d3	        ; else signal ReadRest to throw away packet
	jsr     2(a4)	        ; ReadRest
	jsr     IPEnable        ; re-enable IP receive -- IPEnable will not work
				; if called in a re-entrant way, so do this at the
				; high interrupt priority
	lea     failcnt,a0
	addq.w  #1,(a0)
	rts
@5
	movea.l IPrdpkt,a5      ; get packet address to store packet length
	move.w  d1,nb_len(a5)   ; IPrdpkt^.nb_len := d1 (pkt length from caller)
	move.w  #IPsize,d3      ; size of buffer for ReadRest
	movea.l IPrdbuf,a3      ; get address of buffer arg to ReadRest
	jsr     2(a4)	        ; ReadRest

; disable reception of further packets for now
	lea     IPrdbuf,a0
	clr.l   (a0)

; put the packet on the IP queue and wake up the IP demultiplexer

	lea     IPrdpkt,a0
	move.l  (a0),-(sp)	        ; push arg for later ip_wake call
	jsr     IPEnable	        ; can't be re-entrant
	move.w  vSCCEnable(a2),sr       ; decrease priority
	move.l  (sp)+,a0	        ; pop arg for ip_wake
	lea     Ticks,a1	        ; a1 := address of Ticks
	move.l  (a1),nb_tstamp(a0)      ; packet's timestamp := Ticks
	jsr     ip_wake
	rts

; The packet wasn't for IP.  It had better be for ARP or I'll take my
; ball and go home.

@4
	cmpi.b  #AB_ARP,d0      ; is this a packet for ARP?
	beq.s   @9	        ; if so, continue to process it
	bra.s   recvfail
@9

; ARP packet handling: it's simple because there's just one fixed buffer

	lea     ARPAllowed,a3
	tst.b   (a3)	        ; is ARP allowed right now?
	bne.s   @10	        ; if not zero, continue
	bra.s   recvfail
@10
	move.w  #ARPsize,d3     ; size of ARP buffer for ReadRest
	lea     ARPbuffer,a3    ; address of ARP buffer for ReadRest
	jsr     2(a4)	        ; call ReadRest
	tst.l   d3	        ; check return status from ReadRest
	beq.s   @11	        ; if not exactly ARPsize received, return
	rts
@11
	lea     ARPAllowed,a0
	clr.b   (a0)	        ; disallow further ARP until processed
				; by ARP task (which calls ARPEnable)
	move.w  vSCCEnable(a2),sr       ; lower priority for the wakeup
	jsr     arp_wake	 ; wake up ARP handling task
	rts

; demultiplex failure -- read the rest of the packet into nowhere
recvfail
	clr.w   d3	        ; signals ReadRest to throw away packet
	jsr     2(a4)	        ; ReadRest
	lea     failcnt,a0
	addq.w  #1,(a0)
	rts

; ARPEnable: just set ARPAllowed to 1

	.PROC   ARPEnable
	.DEF    ARPAllowed
	lea     ARPAllowed,a0
	move.b  #1,(a0)
	rts
ARPAllowed      .BYTE	        ; 0 if ARP reception not allowed right now

; initialization routine for the socket listener, called from Pascal

	.PROC   listeninit
	.DEF    IPrdbuf,IPrdpkt,ipqaddr,dmxaddr,freeqaddr,failcnt,ATaskAddr
	.REF    ipenable
	lea     dmxaddr,a0
	move.l  16(sp),(a0)
	lea     ATaskAddr,a0
	move.l  12(sp),(a0)
	lea     ipqaddr,a0
	move.l  8(sp),(a0)
	lea     freeqaddr,a0
	move.l  4(sp),(a0)
	lea     failcnt,a0
	clr.w   (a0)
	jsr     ipenable
	rts
	.align  2
failcnt .WORD   ; count of number of failures
IPrdbuf .LONG   ; pointer to packet buffer for IP read
IPrdpkt .LONG   ; pointer to packet for IP read
ipqaddr .LONG   ; address of demuxer queue
dmxaddr .LONG   ; address of demuxer task
freeqaddr .LONG ; address of free packet queue
ATaskAddr .LONG ; address of ARP task

	.PROC   arp_wake
	.REF    ATaskAddr,tk_wake
	lea     ATaskAddr,a0
	move.l  (a0),-(sp)
	jsr     tk_wake
	rts

; ip_wake takes a single parameter, the address of the received packet, in a0

	.PROC   ip_wake
	.REF    ipqaddr,dmxaddr,tk_wake
	lea     ipqaddr,a1      ; queue arg to enqueue (ip_queue address)
	move.l  (a1),a1
	_Enqueue
	lea     dmxaddr,a0
	move.l  (a0),-(sp)      ; task arg to tk_wake (ipdemux address)
	jsr     tk_wake
	rts

; ipenable was formerly a Pascal routine.  It tries to enable the next read by
; pulling a packet off the free packet queue.

	.PROC   ipenable
	.REF    IPrdpkt,IPrdbuf,freeqaddr
	move.l  a4,-(sp)        ; save a4; used as local variable
	lea     freeqaddr,a0
	move.l  (a0),a1	        ; a1 := address of free queue
	tst.l   qHead(a1)       ; is free queue empty?
	beq.s   nofree	        ; if so, go to nofree
	move.l  qHead(a1),a0    ; element arg to dequeue
	move.l  a0,a4	        ; save free packet addr in a4
	_Dequeue
	tst.w   d0	        ; did dequeue operation succeed? (d0 is status)
	bne.s   nofree	        ; if not, go to nofree
	lea     IPrdpkt,a0
	move.l  a4,(a0)
	lea     IPrdbuf,a0
	move.l  nb_buff(a4),(a0)
	bra.s   return
nofree
	lea     IPrdpkt,a0      ; clear packet address
	clr.l   (a0)
	lea     IPrdbuf,a0      ; clear buffer address
	clr.l   (a0)
return
	move.l  (sp)+,a4        ; restore pushed a4
	rts

; getarpbuf: returns the address of the ARP buffer, for use by Pascal code
; FUNCTION getarpbuf:ARP_PACKET; EXTERNAL;

	.FUNC   getarpbuf
	.DEF    ARPbuffer
	lea     ARPbuffer,a0
	move.l  a0,4(sp)
	rts
	.align  2
ARPbuffer       .block  ARPsize ; buffer used for receiving ARP packets

; FUNCTION lfailures:INTEGER;

	.FUNC   lfailures
	.REF    failcnt
	lea     failcnt,a0
	move.w  (a0),4(sp)
	rts

.END
!E!O!F!
#
#
echo extracting net/name_host.text...
cat >net/name_host.text <<'!E!O!F!'
{$X-}
{$M+}
{$D-}
{$R-}
{$0V-}
{$DECL DEBUG}
{$SETC DEBUG := false}
UNIT NameHost;

{ Please note the copyright notice in the file "copyright/notice" }

INTERFACE

{$L-}
   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-IP_Lib	  } IP_Lib;

{$L+}

FUNCTION NameRemoteHost(var name:STR255):Boolean;

IMPLEMENTATION

CONST FHostDialog = 31;
      OKbut = 1;
      CancelBut = 2;
      NameItem = 3;

FUNCTION NameRemoteHost(var name:STR255):Boolean;
VAR itemHit:INTEGER;
    MyDialog:DialogPtr;
    itemType:INTEGER;
    itemHndl:Handle;
    itemBox:Rect;
BEGIN
   MyDialog := GetNewDialog(FHostDialog,NIL,POINTER(-1));
   GetDItem(MyDialog,NameItem,itemType,itemHndl,itemBox);
   SetIText(itemHndl,DefaultHost);
   SelIText(MyDialog,NameItem,0,16000);
   ModalDialog(NIL,itemHit);
   if itemHit = 1 then GetIText(itemHndl,name);
   DisposDialog(MyDialog);
   NameRemoteHost := (itemHit = 1);
END;

END.
!E!O!F!
#
#
echo extracting net/name_user.text...
cat >net/name_user.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
UNIT Name_User;

{ Please note the copyright notice in the file "copyright/notice" }

INTERFACE

{$L-}

   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-err_lib	  } Err_Lib,
      {$U net-ip_lib	  } IP_Lib,
      {$U net-udp_lib	  } UDP_Lib;

{$L+}

CONST

{ Name user return codes }
	NAMETMO =	        1      { Name user timed out on all requests };
	NAMEUNKNOWN     =       0      { Name not known };

FUNCTION udpname(var name: STR255): in_name;
FUNCTION convert_name(var name: STR255): in_name;
FUNCTION resolve_name (var name: STR255): in_name;

IMPLEMENTATION

CONST
	NAMESOCK       = 42;	        { A Well Known Socket }

	NI_NAME =       1;
	NI_ADDR =       2;
	NI_ERR  =       3;

{ This is the header for the nameserver stuff that sits on UDP. }
TYPE

    Ref_nmitem = ^ nmitem;

    nmitem = PACKED RECORD
	     { + 0 } nm_type: byte;
	     { + 1 } nm_len: byte
	     END;

{$S	   }

VAR
	nresp: Integer; { := 0 }        { # of responses to name request }
	name_conns: ARRAY [0..4] OF UDPCONN;
	address: in_name;
	name_task: Ref_Task;


{ Resolve a host name into an internet address.	 Three name formats are
 * accepted:
 *      1) A character string host name
 *      2) An octal host number, in the form:
 *	        <net>,<subnet>,<rsd>,<host>
 *	   or a decimal host number, in the form:
 *	        <net>.<subnet>.<rsd>.<host>
 *	   Any of the <net>, <subnet>, and <rsd> may be left blank or left out
 *	   entirely; they default to the local net/subnet.
 *      3) A thirty-two bit hex number, preceeded by a '#', which is used
 *	   without interpretation as the host number.
 * If a character string name is supplied, it is first looked up in a
 * local host table.  If it is not found there, the routine goes off to
 * internet name servers to try to resolve the name.
 *
 * The following routines are included in this file:
 *      resolve_name    Resolve a name as specified above
 *      gethmch	        Parse a hex machine address specification
 *      getomch	        Parse an octal machine address specification
 }

CONST	 INSZ	 =	 4;

	 MYNET	 = 128;	        { CMU B class network numbers }
	 MYSNET	 = 2;	        {  "	 "      "       "     }
	 MYRSD	 = 0;	        { Empty subnet and host values}
	 MYHOST	 = 0;

TYPE
	ByteName = PACKED ARRAY[1..INSZ] OF BYTE;

	Addr_Type = PACKED RECORD CASE Integer OF
		0: (bytes: ByteName);
		1: (name: in_name);
		END;

FUNCTION getomch(var name: STR255): in_name; forward;
FUNCTION gethmch(var name: STR255): in_name; forward;
FUNCTION getdmch(var name: STR255): in_name; forward;

{ Resolve foreign host internet address
 * Scan table of host names and nicknames.
 * For each name, see if our string is a prefix.  If so, keep checking -
 * could be ambiguous.
 * If ambiguous, return 0.
 * When find no matches, try internet name servers.
 }

{ resolve_name resides in the blank segment.  This is so it can unload the
  name service segment when it completes.  It is an infrequently used protocol,
  and one not time-critical. -- Tim }

{$S	  }

FUNCTION resolve_name(var name: STR255): in_name;
VAR result:in_name;
BEGIN
	result := convert_name(name);
	if result = 0 then result := udpname(name);
	UnloadSeg(@udpname);
	resolve_name := result;
END;

FUNCTION convert_name(var name: STR255): in_name;
VAR L,i,limit:Integer;
    flag:Boolean;
BEGIN
    L := Length(name);
    if L = 0 THEN
	    convert_name := 0
    else if name[1] in ['0'..'9'] THEN BEGIN
	    flag := false;
	    if L < 8 then limit := L else limit := 8;
	    for i := 2 to limit do begin
		if (name[i] = '.') then flag := true end;
	    if flag
		THEN convert_name := getdmch(name)
		ELSE convert_name := getomch(name);
	    END
    else if (name[1] = '#') OR (name[1] = '$') THEN
	    convert_name := gethmch(name)
    else convert_name := 0;
    UnloadSeg(@gethmch);
END; { convert_name }

{$S UDPNameS  }

{ Parse foreign host number input as hex string }

FUNCTION gethmch(var name: STR255): in_name;
VAR Result: LongInt;
    i: Integer;
BEGIN
	IF NOT (Length(name) in [2..9])	 THEN
		gethmch := 0
	else
		begin
		Result := 0;
		for i := 2 to length(name) do
			if name[i] in ['0'..'9'] then
				Result := Result * 16 + (ord(name[i])-ord('0'))
			else if name[i] in ['a'..'f'] then
				Result := Result * 16 + (ord(name[i])-ord('a')+10)
			else if name[i] in ['A'..'F'] then
				Result := Result * 16 + (ord(name[i])-ord('A')+10)
			else Result := Result * 16;
			gethmch := Result;
		end;
END; { gethmch }

{ Parse foreign host number input as octal string }

FUNCTION getomch(var name: STR255): in_name;
LABEL
	8890, 9999; { Loop exits }
VAR
	tmp: ByteName;
	i, j: Integer;
	n: Integer;
	addr: Addr_Type;
	StrCtr: Integer;
	LastFilled: Integer;
BEGIN
	addr.bytes[1] := MYNET;
	addr.bytes[2] := MYSNET;
	addr.bytes[3] := MYRSD;
	addr.bytes[4] := MYHOST;

	StrCtr := 1;
	FOR i := 1 to INSZ DO BEGIN
		n := 0;
		WHILE (StrCtr <= Length(name)) AND (name[StrCtr] in ['0'..'7']) DO
			BEGIN
			n := (n * 8) + ORD(name[StrCtr]) - ORD('0');
			if (n > 255) THEN BEGIN
				getomch := 0;
				EXIT(getomch);
				END;
			StrCtr := StrCtr + 1;
			END;
		tmp[i] := n;
		LastFilled := i;
		if StrCtr > length(name) THEN GOTO 8890;
		if name[StrCtr] = ',' THEN StrCtr := StrCtr + 1
		ELSE BEGIN
		    getomch := 0;
		    EXIT(getomch);
		    END;
		END;

	8890:
	if StrCtr <= length(name) THEN BEGIN
		getomch := 0;
		EXIT(getomch);
		END;

	FOR j := 4 DOWNTO 1 DO BEGIN
		addr.bytes[j] := tmp[LastFilled];
		LastFilled := LastFilled - 1;
		if LastFilled < 1 THEN GOTO 9999;
		END;
9999:
	getomch := addr.name;
END; { getomch }

{ Parse foreign host number input as decimal string }

FUNCTION getdmch(var name: STR255): in_name;
LABEL
	8890, 9999; { Loop exits }
VAR
	tmp: ByteName;
	i, j: Integer;
	n: Integer;
	addr: Addr_Type;
	StrCtr: Integer;
	LastFilled: Integer;
BEGIN
	addr.bytes[1] := MYNET;
	addr.bytes[2] := MYSNET;
	addr.bytes[3] := MYRSD;
	addr.bytes[4] := MYHOST;

	StrCtr := 1;
	FOR i := 1 to INSZ DO BEGIN
		n := 0;
		WHILE (StrCtr <= Length(name)) AND (name[StrCtr] in ['0'..'9']) DO
			BEGIN
			n := (n * 10) + ORD(name[StrCtr]) - ORD('0');
			if (n > 255) THEN BEGIN
				getdmch := 0;
				EXIT(getdmch);
				END;
			StrCtr := StrCtr + 1;
			END;
		tmp[i] := n;
		LastFilled := i;
		if StrCtr > length(name) THEN GOTO 8890;
		if name[StrCtr] = '.' THEN StrCtr := StrCtr + 1
		ELSE BEGIN
		    getdmch := 0;
		    EXIT(getdmch);
		    END;
		END;

	8890:
	if StrCtr <= length(name) THEN BEGIN
		getdmch := 0;
		EXIT(getdmch);
		END;

	FOR j := 4 DOWNTO 1 DO BEGIN
		addr.bytes[j] := tmp[LastFilled];
		LastFilled := LastFilled - 1;
		if LastFilled < 1 THEN GOTO 9999;
		END;
9999:
	getdmch := addr.name;
END;

{ This code implements a UDP name user compatible with the servers on
	Mit-Multics, Mit-XX and Mit-Spooler.  }

PROCEDURE name_rcv(p: PACKET; len: Integer; host: in_name; foo_data: PTR);
	forward;

PROCEDURE name_wake(Dummy:PTR);
BEGIN
	tk_wake(name_task);
END;

FUNCTION udpname(var name: STR255): in_name;
LABEL   8888;
CONST   NameTimeout = 9;
VAR     len,i,Dummy:Integer;
	p:PACKET;
	s:PTR;
	tm:ref_Timer;
BEGIN
	{ Check the local table for a match }
	{ Note: This code is missing: some day it should be added.   }

	{ grovel, grovel...check if the name is 'me'. If it is, special case
		it and use my net 0 ip address }
	if name = 'me' THEN BEGIN
		udpname :=  LocalIPaddr;
		EXIT(udpname);
		END;

	len := length(name);

	p := udp_alloc(len + 3, 0);
	if p = NIL THEN BEGIN
		CantAlloc(StrCvt('UDPNAME'),StrCvt('packet'));
		udpname := 0;
		EXIT(udpname);
		END;

	s := POINTER(ORD4(udp_data(udp_head(in_head(p)))));
	s^ := NI_NAME;
	s := POINTER(ORD4(s)+1);
	s^ := len;
	s := POINTER(ORD4(s)+1);
	for i := 1 to len do
		begin
		s^ := byte(name[i]);
		s := POINTER(ORD4(s)+1);
		end;
	s^ := 0;

	name_task := tk_cur;
	address := 0;
	nresp := 0;

	FOR i := 0 TO Num_Name_Servers DO BEGIN
		if i = Num_Name_Servers THEN
			BEGIN
{$IFC DEBUG}
			if BCBitAnd(NDEBUG,CBitOr(PROTERR,NETERR)) THEN
				WriteLn('UDP_NAME: Too many name servers!!');
			if BCBitAnd(NDEBUG,BUGHALT) THEN BEGIN
				WriteLn('BUGHALT!');
				HALT;
				END;
{$ENDC}
			GOTO 8888; {break;}
			END;

		name_conns[i] := udp_open(NSIPAddress[i], NAMESOCK, udp_socket,
				     @name_rcv, NIL);

{$IFC DEBUG}
		if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN
			Write('NAME: Sending request to server ');
			out_inaddr( NSIPAddress[i] );
			WriteLn('.');
			END;
{$ENDC}

		Dummy := udp_write(name_conns[i], p, len+3);
		END; { end of for loop }
		8888:

	udp_free(p);

	tm := tm_alloc;
	if tm=NIL THEN BEGIN
		CantAlloc(StrCvt('UDPNAME'),StrCvt('timer'));
		udpname := 0;
		exit(udpname);
		END;
	tm_set(NameTIMEOUT, @name_wake, NIL, tm);
	tk_block;

	{ Now one of two things should have happened: we should have received
		the resolved address or we should have timed out. If we've
		gotten the address, we should clear the timer. If we've timed
		out, we should just deallocate it. }

	Dummy := ORD(tm_clear(tm));
	Dummy := ORD(tm_free(tm));

	{ Clean up the udp connections. }

	for i:=0 to Num_Name_Servers do udp_close(name_conns[i]);

	if (nresp = 0) THEN udpname := NAMETMO
	else udpname := address;
END;

PROCEDURE name_rcv{(p: PACKET; len: Integer; host: in_name; foo_data: PTR)};
VAR
	pnm: Ref_nmitem;
	pname:Ref_in_name;
BEGIN
	nresp := nresp + 1;

{$IFC DEBUG}
	if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN
		Write('NAME_RCV: Got response from foreign host ');
		out_inaddr(host);
		WriteLn('.');
		END;
{$ENDC}

	pnm := {(struct nmitem *)} POINTER(ORD4(udp_data(udp_head(in_head(p)))));

	pnm := {(struct nmitem *)} POINTER(ORD4(pnm) + pnm^.nm_len + 2);

	if (pnm^.nm_type = NI_ADDR) AND (address = 0) THEN BEGIN
		name_wake(NIL);
		pname := POINTER(ORD4(pnm)+2);
		address := pname^;
		END
	else BEGIN
{$IFC DEBUG}
	  if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN
		Write('NAME_RCV: Name server ');
		out_inaddr(host);
		WriteLn(' couldn''t resolve the name.');
		END;
{$ENDC}
		if (nresp = Num_Name_Servers) THEN name_wake(NIL);
		END;

	udp_free(p);
END { name_rcv };

END.

!E!O!F!
#
#
echo extracting net/task_asm.text...
cat >net/task_asm.text <<'!E!O!F!'
	  .Title 'Task Switching routines'
;__________________________________________________________________
;
; Pascal interface to the Task Switching Routines
;
; Mark Sherman, July 1984
; Version 0.0
;
;__________________________________________________________________

     .nomacrolist
     .PAGE
;___________________________________________________________
;
; Definitions of Pascal data structure offsets
;___________________________________________________________
;
;
;	 task = RECORD			 { a task - top of its stack }
;	     tk_sp: PTR;		 { task's current stack ptr }
;	     tk_nxt: Ref_Task;		 { pointer to next task }
;	     tk_size: Integer;		 { Size of allocated task }
;	     tk_unique: Ref_Task;	 { Unique Identification for collision }
;	     ev_flg: Task_State;	 { flag set if task is scheduled }
;	     tk_stack: packed array[0..0] of stack;  { task's stack }
;	 END;
;
	  .PROC	    tk_swtch,2
;___________________________________________________________________________
;
;    tk_switch - Task Switch; swap the current task with the proposed task
;
;	  Call:	    Old_Task: Task to block
;		    New_Task: Task to start running
;
;
;    PROCEDURE tk_swtch(Old_Task:Ref_Task;New_Task:Ref_Task);
;
;    Stack setup upon entry:
;       +0  .LONG     Return address
;       +4  .LONG     Address of New_Task (+0 after popping RA)
;       +8  .LONG     Address of Old_Task (+4 after popping RA)
;
;    Other stack perversity:
;	  When entering, all of the registers execpt for A0, A1 and D0 belong
;	  to the old task. These will be stacked on the old stack and replaced
;	  by values from the new stack. Hence a complete context shift is
;	  done, providing the caller of task switch with the environment from
;	  the last block of the task. (Note: by using the old stack, we
;	  will use the return address of that left by the task when it blocked
;	  by calling tk_block. Similarly, the current task's stack will be
;	  saved for its later restarting. Also see tk_fram below
;
;_____________________________________________________________________________
;	   STOP #10;			 ; Drop into debugger

	  LEA	    SaveRA,A0
	  MOVE.L    (SP)+,(A0)	        ; Save the return address
	  MOVEA.L   SP,A1	        ; Hold on to old SP for Args
					; Save all of the registers -- who
					; knows what will be munged
	  MOVEM.L   D3/D4/D5/D6/D7/A2/A3/A3/A4/A5/A6,-(SP)
	  MOVE.L    4(A1),A0	        ; A0 -> TCB for old task
	  MOVE.L    SP,(A0)	        ; Save current SP in TCB (start of rec)
	  MOVE.L    (A1),A0	        ; A0 -> TCB for new task
	  MOVE.L    (A0),SP	        ; Restore the stack pointer
					; Put back the registers
	  MOVEM.L   (SP)+,D3/D4/D5/D6/D7/A2/A3/A3/A4/A5/A6
	  ADD.L	    #8,SP		 ; Pop the stack of arguments
					 ; More trickery: we need return address
	  MOVEA.L   SaveRA,A1		 ; from old stack!
	  JMP	    (A1)		 ; Back to tk_blk !
	  .align        2
SaveRA	  .LONG
;
;
;

	  .PROC	    tk_frame,4
	  .REF	    _cdump
;___________________________________________________________________________
;
;    tk_frame - Setup a frame as if a task had called tk_block
;
;	  Call:	    PTCB: Pointer to a task control block
;		    StackSize: Number of bytes pointed at by PTCB
;		    Proc_Start: Starting address of task
;		    Proc_Arg: Pointer argument for task
;
;	  Returns:  Filled in task control block for use in tk_block
;
;    PROCEDURE tk_frame (PTCB:Ref_Task; StackSize: LongInt;
;			  Proc_Start: ProcPtr; Proc_Arg: PTR);
;
;    Stack setup upon entry:
;	  +0 .LONG     Return address
;	  +4 .LONG     Pointer value for Proc_Arg
;	  +8 .LONG     Address of task code
;	 +12 .LONG     Number of bytes in TCB + Stack
;	 +16 .LONG     Pointer to the TCB -- TCB values at bottom, stack at top
;		       and grows downward. Disaster is stack grows into TCB.
;
;    To properly look like this task had called tk_block (and hence is
;    returning), there must be a stack frame for tk_block. Further, since
;    the return from tk_block will cause the procedure to start, its
;    parameter must also be available.
;
;    What we are trying to simulate is the situation where a call to
;    tk_block has been made, followed by a call to tk_swtch. The simulation
;    of these calls (up till the actual task switch) is
;	  Push args to tk_block = NOP (no arguments)
;	  JSR to tk_block = PUSH return address = start address of task
;	  LINK A6,Locals  => Push saved A6, Assign SP to A6, alloc locals
;	  (Note: this saved A6 will become SP upon return of tk_block)
;	  Push args to tk_swtch (PUSH Oldtask, Push NewTask)
;	  JSR to tkswicth = PUSH return address
;	  POP R.A. inside of tk_swtch
;	  PUSH Saved registers (note the value of A6 and A5!)
;	  Save the SP at this point
;
;
;Hence the stack we must build is:
;
;
;	  parameter for task (since task thinks it is being called!)
;	  return address for "caller" of task = error handling routine
;	  parameters to tk_block to be popped
;	  R.A for tk_block = starting address of task
;  A6->	  linked value of "old" A6 (nonexistant value)
;	  local variables of tk_block
;	  parameters to tk_swtch (only to be popped here)
;	  saved registers from inside of tk_swtch
;	  (save SP must go inside of TCB)
;
;_____________________________________________________________________________
;	   STOP #12
BlockLocs  .Equ	   20			 ; Max space taken by tk_block's locals

	  LEA	    OldA6,A0
	  MOVE.L    A6,(A0)	        ; Save it for later -- we will clobber 6
	  MOVE.L    16(SP),A0	        ; A0 -> TCB
	  ADD.L	    12(SP),A0	        ; A0 -> top of Stack (grows down)
					; So now we start pushing everything
					; on the stack denoted by A0
	  MOVE.L    4(SP),-(A0)	        ; Push the task's parameter
	  LEA	    _cdump,A1	        ; Ret. Addr. of "caller" of the task
	  MOVE.L    A1,-(A0)
;	  NOP			        ; Nothing - no parameters to tk_block yt
	  MOVE.L    8(SP),-(A0)	        ; Push the return address for tk_block =
					; starting address of task
	  MOVE.L    A1,-(A0)	        ; A fake A6 linkage register to UNLNK
					; This is what stack pointer will be
					; loaded with !!!! (See above)
	  MOVEA.L   A0,A6	        ; Here is fake linkage !!!
	  SUBA.L    #BlockLocs,A0       ; Allocate tk_block locals (some value)
	  MOVE.L    A1,-(A0)	        ; Fake Old_Task_Ptr
	  MOVE.L    A1,-(A0)	        ; Fake New_Task_Ptr
					; Put in plenty of registers to restore
	  MOVEM.L   D3/D4/D5/D6/D7/A2/A3/A3/A4/A5/A6,-(A0)
;
; All done setting up stack, so save the stack pointer into TCB
;

	  MOVE.L    16(SP),A1	        ; A1 -> TCB
	  MOVE.L    A0,(A1)	        ; Place SP, i.e., A0, into TCB
DoneIt
	  MOVE.L    (SP)+,A0	        ; Get return address of tk_frame
	  ADDA.L    #16,SP	        ; Pop off the parameters
	  MOVEA.L   OldA6,A6	        ; Restore the frame pointer
	  JMP	    (A0)	        ; return to called of tk_frame
	  .align    2
OldA6	  .LONG

;___________________________________________________________________________
;
;    stk_init - Stack initialization for tasking
;
;	  Call:	    Size: maximum amount of stack space required by main prog
;
;
;    PROCEDURE stk_init(size:Integer);
;
;    Stack setup upon entry:
;       +0  .LONG     Return address
;       +4  .WORD     Number of bytes to allocate beyond current stack
;
;
;____________________________________________________________________________
     .PROC     stk_init,1
     .DEF      GlobStk

     MOVE.L    SP,D0		   ; Save the stack pointer
     MOVE.L    (SP)+,A0		   ; Save the return address
     CLR.L     D1		   ; Clear out the whole word
     MOVE.W    (SP)+,D1		   ; Get the length
     SUB.L     D1,D0		   ; Allocate the hypothetical stack ptr (D0)
     LEA       GlobStk,A1	   ; Just for the store. Sigh.
     MOVE.L    D0,(A1)		   ; Save the magic stack ptr
     JMP       (A0)		   ; And return
     .align    2
GlobStk	  .LONG

;___________________________________________________________________________
;
;    stk_alloc -- allocate a stack for a task
;
;	  Call:	    Size: number of bytes to allocate
;
;	  Returns:  A Pointer to the lowest address (bottom ) of the stack
;
;
;    FUNCTION stk_alloc(size: Integer):PTR;
;
;    Stack setup upon entry:
;       +0  .LONG     Return address
;       +4  .WORD     Number of bytes to provide
;       +6  .LONG     Address of stack block of storage (return value)
;
;
;
;_____________________________________________________________________________
     .FUNC     stk_alloc,1
     .REF      GlobStk
     MOVE.L    (SP)+,A0		   ; Save the return address
     CLR.L     D0		   ; Clear out register for count
     MOVE.W    (SP)+,D0		   ; Get the count
;     STOP #8
     LEA       GlobStk,A1	   ; Get a ptr to the variable for next inst
     SUB.L     D0,(A1)		   ; Advanced the stack
     MOVE.L    GlobStk,(SP)	   ; And return the "bottom" of the stack
     JMP       (A0)		   ; And return

;    utility routine to read the stack pointer.

     .FUNC GetSP,0
     MOVE.L    (SP)+,A0		   ; save the r.a
     MOVEA.L   SP,A1		   ; save the sp
     MOVE.L    A1,(SP)		   ; give back the sp
     JMP       (A0)		   ; return

.END
!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting net/task_lib.text...
cat >net/task_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
UNIT Task_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

{ This file contains the routines which implement the Macintosh multitasking
 * system. Only the (hopefully) machine-independent routines are included
 * herein; the machine dependent routines are in the file
 * net-task_asm. The following routines are included:
 *      tk_init	      initializes the tasking system and creates the first task
 *      tk_fork	        creates additional tasks
 *      tk_block        blocks the current, goes through the circular list
 *		        of tasks task in round robbin order until it finds
 *		        one that is awake and starts it running
 *      tk_wake	        awakens a task by seting its event flag
 *
 * Tasks are allocated dynamically as needed; they may
 * be of any size but their size is fixed once they are allocated. They are
 * then uniquely and permanantly associate with a task control block.
 *
 * Tasks form a circular list that is strung together by pointers (tk_nxt).
 * Currently tasks are placed in the list when they are created and they never
 * change their position.
 * Tasks have three states: blocked, awake, and running. The running task is
 * the task that is currently executing. Only one task runs at any given time
 * and its event flag (ev_flg) determines whether it will be blocked or awake
 * after it finishes running. If a task is not running, it is awake if its
 * event flag is set, and it is blocked if its event flag is not set.
 * tk_block resets a task's event flag just before as it starts to run.
 * A running task may thus awaken itself. A task gives up control of the
 * processor by calling tk_block. Tk_block used the tk_nxt pointer of the
 * task that is giving up control, to find the next task in the circular list.
 * If this next task is awake, tk_block will set it running. If it is blocked,
 * tk_block will use its tk_nxt pointer to find the next element in the
 * circular list. If it is awake, tk_block will set it running. If not,...etc.
 * The currently running task is identified by the global variable tk_cur.
 }

INTERFACE

{$L-}

USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-PackIntf	  } PackIntf,
      {$U net-err_lib	  } Err_Lib;

{$L+}

CONST
	MaxTaskName = 8;	        { Maximum characters in a task name }
TYPE
	stack = byte;		        { type of data in task stacks }
	Ref_Stack = ^ Stack;
	Ref_Task = ^ task;
	Task_State = (Blocked, Awake, Running);
	Task_Name = String[MaxTaskName];

	{ For all but the initial task, the TCB below sits at the very bottom
	  of allocated stack space for the task. Since the stack grows
	  downward, a stack that grows too large will clobber its TCB }

	task = RECORD		        { a task - top of its stack }
	    tk_sp: PTR;		        { task's current stack ptr }
	    tk_nxt: Ref_Task;	        { pointer to next task }
	    tk_size: Integer;	        { Size of allocated task }
	    tk_unique: Ref_Task;        { Unique Identification for collision }
	    ev_flg: Task_State;	        { flag set if task is scheduled }
	    tk_stack: packed array[0..0] of stack;  { task's stack }
	END;

VAR
	Main_Task: Ref_Task;	        { main task - always around }
	tk_cur: Ref_Task;	        { a pointer to the current task }
	TDEBUG: Integer { := 0 };       { DEBUG flag }

PROCEDURE tk_wake(tk:Ref_Task);
PROCEDURE tk_yield;
FUNCTION tk_init(stksiz:Integer): Ref_Task;
FUNCTION tk_fork(prev_tk:Ref_Task; start:ProcPtr; stksiz: Integer;
		 name:Task_Name; arg:Ptr): Ref_Task;
PROCEDURE tk_block;
PROCEDURE tk_exit;

PROCEDURE CheckTask;

PROCEDURE _cdump; { This makes the decl external for the assembly routine }

IMPLEMENTATION

{ Names are kept in a separate table so they can be retrieved when tasks overflow.
  If they are kept in the TCB, they will be overwritten at overflow.  Here we
  declare the table and the routines which manipulate it. }

CONST MAXTASKS = 16;

TYPE    tk_name_entry = packed record
		theTCB:Ref_task;
		theName:Task_Name;
		inUse:Boolean
		end;

VAR     nametable: array [1..MAXTASKS] of tk_name_entry;

PROCEDURE initnames;
VAR i:INTEGER;
BEGIN
	for i := 1 to MAXTASKS do nametable[i].inUse := false;
END;

PROCEDURE nametask(tk:Ref_task; name:Task_Name);
VAR i:INTEGER;
BEGIN
	i := 1;
	while (i <= MAXTASKS) & (nametable[i].inUse) do i := i + 1;
	if i <= MAXTASKS then begin
		nametable[i].theTCB := tk;
		nametable[i].theName := name;
		nametable[i].inUse := true;
		end;
END;

FUNCTION getname(tk:Ref_task):StringPtr;
VAR i:INTEGER;
BEGIN
	for i := 1 to MAXTASKS do
		if nametable[i].inUse then
			if nametable[i].theTCB = tk then begin
				getname := StrCvt(nametable[i].theName);
				exit(getname);
				end;
	getname := NIL;
END;

PROCEDURE delname(tk:Ref_task);
VAR i:INTEGER;
BEGIN
	for i := 1 to MAXTASKS do
		if nametable[i].inUse then
			if nametable[i].theTCB = tk then begin
				nametable[i].inUse := false;
				exit(delname);
				end;
END;

CONST   AlertResource = 666;

VAR
	death: Boolean { :=  false };   { Claim that a task should be killed }
	died: Ref_Task;		        { Task that died }
	Next_tk : Ref_Task;  { the next task to run during scheduling}
			     { This is global to minimize stack munging }
	Free_Task_List: Ref_Task;       { Where tasks that died have their
					  storage placed }
	FirstTask: Task;

FUNCTION GetSP: Ptr; External;

PROCEDURE tk_alert(tk:Ref_Task); forward;

{ Two assembly language routines for munging the stack }

PROCEDURE tk_frame (PTCB:Ref_Task; StackSize: LongInt;
		    Proc_Start: ProcPtr; Proc_Arg: PTR);EXTERNAL;

PROCEDURE tk_swtch(Old_Task:Ref_Task;New_Task:Ref_Task); EXTERNAL;

{ STK_INIT(size) is essentially something else... }

PROCEDURE stk_init(size:Integer); EXTERNAL;

{ STK_ALLOC(size) is sort of what it is. }

FUNCTION stk_alloc(size: Integer):PTR; EXTERNAL;

{ Initialize the tasking system. Create the first task, and set its stack
   pointer to the main program stack. The first task will always use the main
   program stack, even though tk_init sets aside space for a stack of size
   stksiz.(?!) The circular list of tasks contains only the original task,
   so originally set its next task pointer to point to itself. This routine
   returns to the caller with a pointer to the first task.      }

{$S InitSeg }

FUNCTION tk_init(stksiz:Integer): Ref_Task;
VAR
	tk : Ref_Task;	        { pointer to the first task }
	Actual_Size: Integer;
BEGIN   { size of the stack that is never used--in bytes }

	{ CouldAlert(AlertResource); }	   { Need task alert box in memory }

	{ Initialize the globals }
	TDEBUG := 0;		        { DEBUG flag }
	death :=  false ;	        { No task should be killed }
	Free_Task_List := NIL;	        { No killed tasks on the list yet }

	Actual_Size := stksiz + sizeof(task);
	IF Odd(Actual_Size) THEN Actual_Size := Actual_Size + 1;
	{ Now allocate the stack space for the main program to use }
	{ Although the TCB is not really used here, we leave it just in case
	  this stack space is reused. }
	Stk_Init(Actual_Size);

	{ create the first task }
	tk := @FirstTask;

	tk_cur := tk;		        { It is the currently running task }
	tk^.ev_flg := Blocked;	        { Since it is running it does not }
					{ need to be awakened, but it will }
					{ sleep when it blocks unless it }
					{ resets its flag.		 }
	initnames;
	nametask(tk,'Main');
	{ tk^.tk_name := 'Main'; }
	tk^.tk_Size := Actual_Size;
	tk^.tk_unique := @FirstTask;

	tk^.tk_nxt := tk;	        { It is the next task since it is }
					{ the only task.        }
	tk_init := tk;		        { Return the initial task }
END;

{$S	    }

PROCEDURE CheckTask;
{ This checks for task over flow }
BEGIN
    IF tk_cur <> @FirstTask THEN
    IF (ORD4(GetSP) <= ORD4(tk_cur)) OR (tk_cur <> tk_cur^.tk_Unique) THEN
	tk_alert(tk_cur);
END;

PROCEDURE tk_wake(tk:Ref_Task);
BEGIN
     tk^.ev_flg := Awake;
END;

PROCEDURE tk_yield;
BEGIN
	tk_wake(tk_cur);
	tk_block;
END;

{ Create a new task  with stksiz bytes of stack, and place it in the circular
   list of tasks after prev_tk. Awaken it so that it will run, and set it up
   so that when it does run, it will start runing  routine start.
   This routine does not affect the execution of the currently running task.
   It returns a pointer to the new task.        }

FUNCTION tk_fork(prev_tk:Ref_Task; start:ProcPtr; stksiz: Integer;
		 name:Task_Name; arg:Ptr): Ref_Task;
	{task   *prev_tk;}      { predecessor to the new task }
	{int    (*start) ();}   { Where the new task starts execution. }
	{int    stksiz;}        { The size of the stack of the new task. }
	{char   *name;}	        { The task's name as a string }
	{unsigned arg;}	        { argument to the task }
VAR
	tk: Ref_Task;	        { a pointer to the new task }
	TempTK, OTemp: Ref_Task;{ For checking free task list }
	size: integer;	        { size of the new task }
BEGIN
	size := stksiz + sizeof(task);

	IF Odd(size) THEN size := size + 1;

	{ create the new task }
	{ Either reuse some storage from before or create a new block }

	TempTK := Free_Task_List;
	OTemp := NIL;
	tk := NIL;
	WHILE (TempTK <> NIL) AND (tk = NIL) DO
		IF TempTK^.tk_Size >= Size THEN BEGIN
		    { Found a block that was large enough -- any fit strategy }
		    tk := TempTK;
		    Size := TempTK^.tk_Size;    { Don't lose any space }
		    IF OTemp = NIL
			THEN Free_Task_List := TempTK^.tk_nxt
			ELSE OTemp^.tk_nxt := TempTK^.tk_nxt;
		    END
		ELSE BEGIN
		    OTemp := TempTK;
		    TempTK := TempTK^.tk_Nxt;
		    END;

	IF tk = NIL THEN tk := { (task *)}  POINTER(ORD4(stk_alloc (size)))  ;

	{ set it up to run }

	tk_frame (tk, size, start, arg);

	tk^.ev_flg := Awake;	        { Schedule the task to run. }

	tk^.tk_nxt := prev_tk^.tk_nxt;	   { Fit it in after prev_tk. }
	prev_tk^.tk_nxt := tk;

	{ tk^.tk_name := name; }	    { Set its name }
	nametask(tk,name);
	tk^.tk_Size := size;		    { Set its size }
	tk^.tk_unique := tk;		    { Set its unique ID }

{$IFC DEBUG}
	Write('Task ',name,' has TCB at ');
	WriteLong(ORD4(tk));WriteLn('');
{$ENDC}
	tk_fork := tk;		        { Return ptr to TCB }
END;

{ Block the currently running task and run the next task in the circular list
   of tasks that is awake. Before returning, see if any cleanup has to be done
	for another task.
}

PROCEDURE tk_block;
BEGIN
	CheckTask;	     { Make sure we are still OK ! }

	Next_tk := tk_cur;   { Get the current task and block it }

{$IFC DEBUG}
	IF (TDEBUG <> 0) THEN
		WriteLn('TASK: Task blocking: ', getname(tk_cur)^);
{$ENDC}

	REPEAT
		Next_tk := Next_tk^.tk_nxt;
		IF Next_tk <> Next_tk^.tk_Unique THEN tk_alert(tk_cur);
	UNTIL (Next_tk^.ev_flg <> Blocked);

	Next_tk^.ev_flg := Blocked;	  { Reset its event flag before it runs }

	if Next_tk <> tk_Cur THEN tk_swtch (tk_cur,Next_tk);	 { Run the next task. }

	tk_cur := Next_tk;

{$IFC DEBUG}
	IF (TDEBUG <> 0) THEN
		WriteLn('TASK: Task now running: ', getname(tk_cur)^);
{$ENDC}

	IF death THEN BEGIN
		{ free up the task }
		death := FALSE;
		{ cfree(POINTER(ORD4(died))); }
		{Can't free stack in the Mac, so instead we just keep a list
		 of the old stack space for possible reuse }
		died^.tk_Nxt :=	 Free_Task_List;
		Free_Task_List := died;
		END;
END; { end of tk_block }


{ tk_exit() : destroy the current task. Accomplished by setting a flag
	indicating that an exit has occured and then entering the scheduler
	loop. When tk_block() returns for some other task and finds this
	flag set, it deallocates the task which exited. This is nry
	because we still need a stack to run on.

	The task removes itself from the circular list of tasks in the system
	so that it cannot be awoken after it has exited. Otherwise, the exit
	might be done in the context of the task itself, which would prove
	disastrous.

	Yes, this routine never returns (not really).
}

PROCEDURE tk_exit;
VAR     tk: Ref_Task;
BEGIN
	{ hunt for the task which tk_cur is the successor of }
	tk := tk_cur;
	WHILE (tk^.tk_nxt <> tk_cur) DO tk := tk^.tk_nxt;

	{ now patch around tk_cur }
	tk^.tk_nxt := tk_cur^.tk_nxt;

	death := TRUE;
	died := tk_cur;
	delname(tk_cur);
	tk_block;
	Fatal(StrCvt('Disaster: tk_exit() returning!!!'),false);
END; { end of tk_exit }

PROCEDURE _cdump;
BEGIN
    Fatal(StrCvt(concat('Task ',getname(tk_cur)^,' is trying to return')),false);
END; { end of _cdump }

PROCEDURE tk_alert(tk:Ref_Task);
VAR dummy:INTEGER;
    SPStr,TCBStr:STR255;
BEGIN
	NumToString(ORD4(GetSP),SPStr);
	NumToString(ORD4(tk_cur),TCBStr);
	ParamText(SPStr,TCBStr,getname(tk)^,'');
	dummy := StopAlert(AlertResource,NIL);
	reboot;
END;

END.
!E!O!F!
#
#
echo extracting net/term_lib.text...
cat >net/term_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R+}
{$0V-}
{$D+}
{$DECL DEBUG}
{$SETC DEBUG := false}
{$DECL VT102}
{$SETC VT102 := false}
UNIT Term_Lib;

{
	The emulation code is taken from, and modified to Pascal from,
	the source for Macintosh Kermit... which is:

	Copyright (c) 1985, Trustees of Columbia University, New York.
	Non-commercial use permitted with this notice.
}

INTERFACE

{$L-}
   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf;

PROCEDURE em_init;
PROCEDURE em_reset;
PROCEDURE em(c:char);
PROCEDURE EmStr(s:STR255);
PROCEDURE EmLn(s:STR255);
PROCEDURE em_input(eptr:PTR; VAR s:str255);
PROCEDURE em_flush;
PROCEDURE IoUpdate;
PROCEDURE IoIdle;
PROCEDURE IoActivate(modifiers:INTEGER);
PROCEDURE IoClick(where: INTEGER; pt: Point; modifiers: INTEGER);

VAR myWindow: windowptr;       { the window we're emulating in... }

IMPLEMENTATION

CONST
     RETURN = chr($d);	        { carriage return }
     LINEFEED = chr($a);        { linefeed }
     C_L = chr($c);	        { Control-L }
     MAXLIN = 24;	        { Number of lines of text on screen }
     MAXCOL = 80;	        { Number of chars across screen }
     NUMTABS = 9;	        { max number of tabstops on line }
     LINEHEIGHT = 12;	        { pixels height per line of text in window }
     TOPMARGIN = 3;
     BOTTOMMARGIN = 301;        { (LINEHEIGHT * MAXLIN + TOPMARGIN) }
     LEFTMARGIN = 3;
     LINEADJ = 3;	        { amount of char below baseline for row }
     RIGHTMARGIN = 483;	        { (CHARWIDTH * MAXCOL + LEFTMARGIN) }
     CF_OUTC = 0;	        { character type flags }
     CF_SESC = 1;
     CF_MESC = 2;
     CF_TOSS = 3;

TYPE
    sparm = array [0..5] of SignedByte;
VAR
    scroll: rect;
    lastblink: LongInt;
    bmargin: Integer;
    nxtlin: array [0..MAXLIN-1] of Integer; { Linked list of lines }
    botlin: Integer;
    scrtop, scrbot: Integer;
    tabstops: array[0..NUMTABS-1] of integer;
    insert,		        { insert or overwrite text? }
    autowrap:   Boolean;        { autowrap on exceed right margin? }
    scr: array[0..MAXLIN-1] of packed array[0..MAXCOL-1] of char;
    toplin: integer;	        { offset of top of screen from top of array }
    abslin: integer;
    curlin, curcol: integer;    { cursor position }
    savlin, savcol: integer;    { saved cursor position }
    charflg: integer;
    invmode: Boolean;	        {reverse video mode}
    textstyle: style;
    prvchr: char;
    width: integer;	        { width of characters }
    outbuf: packed array[0..MAXCOL] of char;
    outcnt,outcol: Integer;
    dumptr: RgnHandle;
    num1, num2: integer;        { numeric version of parameters }
    numone, numtwo: sparm;
    cursor_on: boolean;	        { cursor currently drawn }
    numptr: Ptr;

{$S Terminal}

procedure makerect( var r: rect; lin, col, numlin, numcol: integer );
{ make a rectangle given the passed boundaries }
begin
  r.top := lin * LINEHEIGHT + TOPMARGIN;
  r.left := col * width + LEFTMARGIN;
  r.bottom := r.top + numlin * LINEHEIGHT;
  r.right := r.left + numcol * width;
end;

PROCEDURE flushbuf;
var r:rect;
e:EventRecord;
begin
	if outcnt = 0 then exit(flushbuf);
	makerect(r,abslin,outcol,1,outcnt);
	if invmode then FillRect(r,black) else EraseRect(r);
	DrawText(@outbuf,0,outcnt);
	outcnt := 0;
	ObscureCursor;
end;

PROCEDURE buf_char(c:char);
begin
	if outcnt = 0 then outcol := curcol;
	outbuf[outcnt] := c;
	outcnt := outcnt + 1;
end;

FUNCTION fndrel(linum:Integer):Integer;
VAR lin,i:Integer;
begin
	lin := toplin;
	for i := 0 to linum - 1 do lin := nxtlin[lin];
	fndrel := lin;
end;

FUNCTION fndabs(linum:Integer):Integer;
VAR i,lin:Integer;
BEGIN
	lin := toplin;
	i := 0;
	while lin <> linum do begin
		i := i + 1;
		lin := nxtlin[lin];
		end;
	fndabs := i;
END;

FUNCTION fndprv(linum:Integer):Integer;
VAR lin:Integer;
BEGIN
	lin := toplin;
	while nxtlin[lin] <> linum do lin := nxtlin[lin];
	fndprv := lin;
END;

procedure relmove( hor, ver: integer );
{ move a relative number of lines and chars.  Both can be negative }
begin
   Move(hor*width,ver*LINEHEIGHT);
   abslin:= abslin + ver;
   curcol:= curcol + hor;
   curlin:= fndrel(abslin);
end;

procedure absmove( hor, ver: integer );
{ move to absolute row and column as specified. }
begin
   MoveTo(hor*width+LEFTMARGIN,(ver+1)*LINEHEIGHT+TOPMARGIN-LINEADJ);
   abslin := ver;
   curcol := hor;
   curlin := fndrel(ver);
end;

procedure zeroline( n: integer );
{ wipe specified line of the RAM version of the screen to all spaces }
var
    index: integer;
begin
    for index := 0 to MAXCOL-1 do scr[n, index] := ' ';
end;

{ terminal operations }

procedure cursor_erase;
begin
	Line(-width,0);
	cursor_on := false;
end;

procedure cursor_draw;
begin
	Line(width,0);
	cursor_on := true;
end;

procedure back_space;
begin
  if (curcol > 0) then relmove(-1,0);
end;

procedure carriage_return;
begin
  absmove(0, abslin);
end;

procedure rev_line_feed;
VAR tbot, ttop, tlout: Integer;
begin
    if curlin = scrtop then begin
	 scrollrect(scroll,0,lineheight,dumptr);
	 zeroline(scrbot);
	 tbot := scrbot;
	 ttop := scrtop;
	 tlout := nxtlin[scrbot];
	 nxtlin[scrbot] := scrtop;
	 scrtop := scrbot;
	 scrbot := fndprv(scrbot);
	 if ttop = toplin then toplin := scrtop
	 else nxtlin[fndprv(ttop)] := scrtop;
	 if tbot = botlin then begin
		botlin := scrbot;
		nxtlin[botlin] := -1;
		end
	 else   nxtlin[scrbot] := tlout;
	 curlin := scrtop;
	 end
    else relmove(0,-1);
end;

procedure line_feed;
VAR tbot, ttop, tlout: Integer;
begin
    if curlin = scrbot then begin
	 scrollrect(scroll,0,-lineheight,dumptr);
	 zeroline(scrtop);
	 tbot := scrbot;
	 ttop := scrtop;
	 tlout := nxtlin[scrbot];
	 nxtlin[scrbot] := scrtop;
	 scrbot := scrtop;
	 scrtop := nxtlin[scrtop];
	 if ttop = toplin then toplin := scrtop
	 else nxtlin[fndprv(ttop)] := scrtop;
	 if tbot = botlin then begin
		botlin := scrbot;
		nxtlin[botlin] := -1;
		end
	 else   nxtlin[scrbot] := tlout;
	 curlin := scrbot;
	 end
    else relmove(0,1);
end;

procedure insert_char;
VAR i:Integer;
    r:Rect;
begin
     makerect(r,abslin,curcol,1,MAXCOL-curcol);
     ScrollRect(r,width,0,dumptr);
     for i := MAXCOL-1 downto curcol+2 do scr[abslin,i-1] := scr[abslin,i];
     scr[abslin,curcol] := ' ';
end;

procedure erase_char;
{ Erase the current character location to the background color }
var r: rect;
begin
    scr[curlin,curcol] := ' ';
    makerect(r,abslin,curcol,1,1);
    if invmode then FillRect(r,black) else EraseRect(r);
end;

procedure tab;
var i: integer;
begin
  for i := 0 to NUMTABS - 1 do begin
	if (tabstops[i] > curcol) then begin
		absmove(tabstops[i],abslin);
		leave;
		end;
	end;
end;

procedure bell;
begin
  sysbeep(3);
end;

procedure cursor_save;
begin
	savcol := curcol;
	savlin := abslin;
end;

procedure cursor_restore;
begin
	absmove(savcol,savlin);
end;

procedure query_terminal;
{ respond to a request from the host querying our terminal.... }
VAR err:OSErr;
begin
{$IFC VT102}
	err := PostEvent(keyDown,27{escape});
	err := PostEvent(keyDown,ord('['));
	err := PostEvent(keyDown,ord('?'));
	err := PostEvent(keyDown,ord('6'));
	err := PostEvent(keyDown,ord('c'));
{$ENDC}
end;

procedure insert_mode;
begin
	if (prvchr <> '?') & (num1 = 4) then insert := true;
end;

procedure end_insert_mode;
begin
	if (prvchr <> '?') & (num1 = 4) then insert := false;
end;

procedure up;
begin
  if num1 = 0 then num1 := 1;
  relmove(0,-num1);
end;

procedure down;
begin
  if num1 = 0 then num1 := 1;
  relmove(0,num1);
end;

procedure left;
begin
  if num1 = 0 then num1 := 1;
  relmove(-num1,0);
end;

procedure right;
begin
  if num1 = 0 then num1 := 1;
  relmove(num1,0);
end;

procedure cursor_position;
begin
  num1 := num1 - 1;
  if num1 < 0 then num1 := 0;
  num2 := num2 - 1;
  if num2 < 0 then num2 := 0;
  absmove(num2,num1);
end;

procedure clear_screen;
{ clear the entire screen to nada.... }
var index: integer;
    r:	   rect;
begin
  makerect(r,0,0,MAXLIN,MAXCOL);
  eraserect(r);
  for index := 0 to MAXLIN - 1 do zeroline(index);
end;

procedure clear_line;
var i: integer;
    r: rect;
begin
  case num1 of
	0:      begin   { here to the right }
		  makerect(r,abslin,curcol,1,maxcol-curcol);
		  for i:= curcol to maxcol - 1 do scr[curlin,i] := ' ';
		end;
	1:      begin   { left to here }
		  makerect(r,abslin,0,1,curcol+1);
		  for i:= 0 to curcol do scr[curlin,i] := ' ';
		end;
	2:      begin   { entire line }
		  makerect(r,abslin,0,1,maxcol);
		  zeroline(curlin);
		end;
  end;
  eraserect(r);
end;

procedure erase_display;
var i: integer;
    r: rect;
begin
  case num1 of
	0:      begin
		  clear_line;
		  makerect(r,abslin+1,0,maxlin-abslin,maxcol);
		  eraserect(r);
		  for i:= abslin+1 to maxlin-1 do zeroline(fndrel(i));
		end;
	1:      begin
		  clear_line;
		  makerect(r,0,0,abslin,maxcol);
		  eraserect(r);
		  for i:= 0 to abslin do zeroline(fndrel(i));
		end;
	2:      clear_screen;
  end;
end;

{$IFC VT102}
procedure insert_line;
{ not part of VT100 code, only VT102 }
VAR r:rect;
begin
	if num1 = 0 then num1 := 1;
	makerect(r,abslin,0,0,MAXCOL);
	r.bottom := bmargin;
	if num1 > fndabs(scrbot) - abslin then
		num1 := fndabs(scrbot) - abslin;
	ScrollRect(r,0,num1*LINEHEIGHT,dumptr);
{ Do the book keeping!!! }
end;

procedure delete_line;
{ not part of VT100 code, only VT102 }
VAR r:rect;
begin
	if num1 = 0 then num1 := 1;
	makerect(r,abslin,0,0,MAXCOL);
	r.bottom := bmargin;
	if num1 > fndabs(scrbot) - abslin then
		num1 := fndabs(scrbot) - abslin;
	ScrollRect(r,0,-num1*LINEHEIGHT,dumptr);
{ Do the book keeping!!! }
end;
{$ENDC}

procedure delete_char;
var i: integer;
    r: rect;
begin
  if num1 = 0 then num1 := 1;
  makerect(r,abslin,curcol,1,maxcol-curcol);
  if (num1 > maxcol-curcol-1) then num1 := maxcol-curcol-1;
  scrollrect(r,-width*num1,0,dumptr);   { scroll the little sucker out... }
  for i := curcol to (maxcol-num1)-1 do scr[abslin,i]:= scr[abslin,i+num1];
  while i< maxcol do begin
    scr[abslin,i]:= ' ';
    i:= i+1;
  end;
end;

procedure text_mode;
begin
  case num1 of
    0:  begin
		invmode := false;
		textstyle := [];
		textface([]);
		textmode(srcor);
	end;
    1:  begin
		textstyle := textstyle + [bold];
		textface(textstyle);
	end;
    4:  begin
		textstyle := textstyle + [underline];
		textface(textstyle);
	end;
    7:  begin
		invmode := true;
		textmode(srcbic);
	end;
    22: if bold in textstyle then begin
		textstyle:= textstyle - [bold];
		textface(textstyle);
	end;
    24: if underline in textstyle then begin
		textstyle := textstyle - [underline];
		textface(textstyle);
	end;
    27: begin
		invmode := false;
		textmode(srcor);
	end;
  end;
end;

procedure home_cursor;
begin
  absmove(0,0);
end;

procedure set_scroll_region;
begin
	num1 := num1 - 1;
	if num1 < 0 then num1 := 0;     { make top of line (prev line) }
	if num2 = 0 then num2 := 24;    { zero means whole screen }
	scroll.top := (num1 * LINEHEIGHT) + TOPMARGIN;
	scroll.bottom := (num2 * LINEHEIGHT) + TOPMARGIN;
	bmargin := scroll.bottom;
	scrtop := fndrel(num1);
	scrbot := fndrel(num2 - 1);
	home_cursor;
end;

{ parsing routines }

procedure do_ctrl( ch: char );
{ perform operation corresponding to the passed control character }
begin
  case ord(ch) of
    7:  bell;
    8:  back_space;
    9:  tab;
    10, 11, 12:
	line_feed;
    13: carriage_return;
    27: charflg := CF_SESC;
  end;
end;

procedure multi_char;
{ initialize state for multi-character escape sequence }
begin
	numone[0] := 0;
	numone[1] := 0;
	numtwo[0] := 0;
	numtwo[1] := 0;
	numptr := @numone;
	prvchr := chr(0);
	charflg := CF_MESC;
end;

procedure do_sesc( ch: char );
{ given a single char, assume it is the second half of a simple escape sequence
  and call the appropriate processing routine.
}
begin
  case ord(ch) of
	35, 40, 41:
		charflg := CF_TOSS;
	55:     cursor_save;
	56:     cursor_restore;
	69:     line_feed;
	77:     rev_line_feed;
	90:     query_terminal;
	91:     multi_char;
  end;
end;

procedure do_mesc( ch: char );
{ given a single char, and up to two numeric parameters, perform the requested
  multi-character escape sequence
}
begin
  case ord(ch) of
	65:     up;
	66:     down;
	67:     right;
	68:     left;
	72:     cursor_position;
	74:     erase_display;
	75:     clear_line;
{$IFC VT102}
	76:     insert_line;	        { not part of VT100 }
	77:     delete_line;	        { not part of VT100 }
{$ENDC}
	80:     delete_char;
	99:     query_terminal;
	102:    cursor_position;
	104:    insert_mode;
	108:    end_insert_mode;
	109:    text_mode;
	114:    set_scroll_region;
  end;
end;

function str_to_int( s: sparm ): integer;
var result: integer;
    index: integer;
begin
  result := 0;
  index := 0;
  while s[index] <> 0 do begin
    if (s[index] >= ord('0')) & (s[index] <= ord('9')) then
      result := (result * 10) + (s[index] - ord('0'))
    else if s[index] = ord('-') then result := -result;
    index := index + 1;
  end;
  str_to_int := result;
end;

procedure MDrawChar( ch: char );
{ actually draw or otherwise handle the char passed to us.  Update all relevent
  pointers }
begin
  if (ch < ' ') then begin
	flushbuf;
	do_ctrl(ch);
	end
  else if ( ord(ch) < 128 ) then begin
    if (curcol >= MAXCOL) then begin    { do we need to handle end of line? }
      if autowrap then begin	        { if so, do we want to autowrap... }
	flushbuf;
	carriage_return;
	line_feed;
	end
      else begin	        { ...or just stick on the right margin? }
	back_space;
	if outcnt > 0 then outcnt := outcnt - 1;
	end;
      end;
    if insert then begin	 { are we in insert mode? }
	insert_char;
	erase_char;
	DrawChar(ch);
	end
    else buf_char(ch);
    { save char in RAM array for update later }
    scr[curlin,curcol] := ch;
    curcol:= curcol+1;
  end;
end;

procedure printit( ch: char );
begin
  ch := chr(BitAnd(ord(ch),$7f));
  case charflg of
    CF_OUTC:    MDrawChar(ch);	        { just output the char }
    CF_SESC:    begin		        { handle single-char escape sequence }
		  charflg := CF_OUTC;
		  do_sesc(ch);
		end;
    CF_MESC:			        { multi-char escape sequences }
		if (ord(ch) >= 32) & (ord(ch) < 64) then begin
		  if (ch >= '<') & (ch <= '?') then prvchr := ch
		  else if ((ch >= '0')&(ch <= '9')) | (ch = '-') | (ch = '+') then
		      begin
		      numptr^ := ORD(ch);
		      numptr := POINTER(ORD4(numptr)+1);
		      numptr^ := 0;
		      end
		  else if (ch = ';') then numptr := @numtwo;
		  end
		else begin	    { we've finished sequence, so }
		  { convert strings numone and numtwo to numbers num1 and num2 }
		  num1 := str_to_int(numone);
		  num2 := str_to_int(numtwo);
		  do_mesc(ch);
		  charflg:= CF_OUTC;
		end;
    CF_TOSS:    charflg := CF_OUTC;     { just ignore this char and go on }
  end;
end;

{ framework routines }

PROCEDURE em_reset;
BEGIN
    numptr := @numone;
    textsize(9);
    textmode(srcOr);
    textstyle := [];
    textface(textstyle);
    outcnt := 0;
    invmode := false;
    insert := false;
    lastblink := TickCount;
    charflg := CF_OUTC;		     { initialize to normal char outputting }
    scroll.left := LEFTMARGIN;
    scroll.right := RIGHTMARGIN;
    scroll.top := TOPMARGIN;
    scroll.bottom := (MAXLIN * LINEHEIGHT) + TOPMARGIN;
    bmargin := scroll.bottom;
    scrtop := toplin;
    scrbot := botlin;
END;

PROCEDURE em_init;
var   index: integer;
      FontNumber: Integer;
      bounds:	  Rect;
      i: Integer;
begin
    GetFNum('monaco',FontNumber);
    bounds.top := 40;
    bounds.left := 4;
    bounds.bottom := 340;
    bounds.right := 508;
    myWindow := newwindow(nil,bounds,concat('MacTELNET of ',COMPDATE),true,
			  4,pointer(-1),false,0);
    setport(myWindow);
    textfont(fontnumber);
    textsize(9);
    textmode(srcOr);
    textstyle := [];
    textface(textstyle);
    dumptr := NewRgn;
    PenMode(patXor);
    width := charwidth('W');
    toplin := 0;
    botlin := MAXLIN - 1;
    for i := 0 to MAXLIN-1 do nxtlin[i] := i + 1;
    nxtlin[botlin] := -1;
    autowrap := true;
    em_reset;
    for index := 0 to NUMTABS-1 do tabstops[index] := 8*(index+1);
    for index := 0 to MAXLIN-1 do zeroline(index);
    clear_screen;
    home_cursor;
    cursor_save;
    cursor_on := false;
end;

PROCEDURE em(c:char);
{ Write character to terminal window }
begin
    if cursor_on then cursor_erase;
    printit(c);
end;

PROCEDURE EmStr(s:STR255);
{ Output a string of chars to screen }
VAR i: INTEGER;
begin
   if cursor_on then cursor_erase;
   for i := 1 to length(s) do printit(s[i]);
end;

PROCEDURE em_flush;
{ makes sure all pending output was done }
BEGIN
   if cursor_on then cursor_erase;
   flushbuf;
END;

PROCEDURE EmLn(s:STR255);
{ Output passed string followed by a carriage return and linefeed }
begin
	EmStr(s);
	flushbuf;
	carriage_return;
	line_feed;
end;

{ event handling routines }

PROCEDURE em_input(eptr:PTR; VAR s:str255);
{ handle a keypress from the user }
VAR e:^EventRecord;
    c:char;
BEGIN
     e := POINTER(ORD4(eptr));
     { For now each character only maps to one character }
     s := 'X';				     {makes it have one character length }
     c := chr(e^.message MOD 128);
     if (BitAND(e^.modifiers,cmdKey) <> 0) & (c >= '@') then begin
	  if (c >= 'a') & (c <= 'z') then
	       c := chr(ord(c) - $20);			      { convert to upper }
	  s[1] := chr(ord(c) - $40)		     { extract control character }
	  end
     else if (c = '`') then begin
	   if (BitAND(e^.modifiers,cmdKey) = 0)
		then s[1] := chr(27) { escape }
		else s[1] := c;
	   end
     else if (c = ' ') then begin
	   if (BitAND(e^.modifiers,cmdKey) = 0)
		then s[1] := ' '
		else s[1] := chr(0); { command-space equals null }
	   end
     else
	  s[1] := c;						   { normal char }
END;

PROCEDURE IoUpdate;
var i,lin: integer;
{ redraw the screen in response to an update event by writing out the contents
  of our RAM image of the screen to the physical screen...
}
BEGIN
   BeginUpdate(myWindow);
   lin := toplin;
   for i := 0 to MAXLIN-1 do begin
       MoveTo(LEFTMARGIN,(i+1)*LINEHEIGHT+TOPMARGIN-LINEADJ);
       DrawText(@scr[lin],0,MAXCOL);
       lin := nxtlin[lin];
   end;
   MoveTo(curcol*width + LEFTMARGIN,
	  (abslin+1)*LINEHEIGHT + TOPMARGIN - LINEADJ);
   cursor_draw;
   EndUpdate(myWindow);
END;

PROCEDURE IoIdle;
{ called repeatedly when nothing is happening... use to blink cursor, etc. }
BEGIN
	flushbuf;
	if TickCount - lastblink >= GetCaretTime then begin
		if not cursor_on then cursor_draw else cursor_erase;
		lastblink := TickCount;
		end;
END;

PROCEDURE IoActivate(modifiers:INTEGER);
BEGIN
END;

PROCEDURE IoClick(where: INTEGER; pt: Point; modifiers: INTEGER);
{ gets called when some fool presses the button on the mouse.... }
BEGIN
END;

END.	 { of unit }

!E!O!F!
#
#
echo extracting net/tftp_defs.text...
cat >net/tftp_defs.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
UNIT TFTP_Defs;

{ Please note the copyright notice in the file "copyright/notice" }

INTERFACE

{$L-}
   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-err_lib	  } Err_Lib,
      {$U net-IP_Lib	  } IP_Lib,
      {$U net-UDP_Lib	  } UDP_Lib;

{$L+}

CONST
{ TFTP randoms }
	GET     = 10	        { GET file from other host to here };
	PUT     = 11	        { PUT file on other host };
	ASCII   = 1	        { transfer as netascii };
	IMAGE   = 2	        { transfer as image };
	TEST    = 3	        { test mode - diskless };
	OCTET   = 4	        { octet mode - same as image };
	MACINTOSH = 5;	        { Macintosh mode - exchange Finder info }

{ TFTP error codes }
	ERRTXT	        = 0     { see the enclosed text };
	FNOTFOUND       = 1     { file not found };
	ACCESS	        = 2     { access violation };
	DISKFULL        = 3     { don't even ask. };
	ILLTFTP	        = 4     { illegal TFTP operation };
	BADTID	        = 5     { unkown transfer ID };
	FEXISTS	        = 6     { file already exists };
	NOUSER	        = 7     { no such user };
{ TFTP opcodes }
	RRQ     = 1	        { read  request };
	WRQ     = 2	        { write request };
	DATA    = 3	        { data packet };
	ACK     = 4	        { acknowledgement packet };
	ERRPCK  = 5	        { error packet };

TYPE
	 tf_FilePart = ( tf_DataPart, tf_RsrcPart, tf_FindPart);

	{ The TFTP connection structure. Contains connection info,and data for
	  timeout calculations. Also, I have added header information for
	  Mac queues so the connections can be kept on tfconnq }

	 Ref_TFconn = ^ tfconn;

	 tfconn = RECORD
		tf_next:Ref_TFconn;     { queue pointer to next element }
		tf_qtype:QTypes;        { second part of queue header }
		tf_udp: UDPCONN;        { udp connection for this transfer }
		tf_outp: PACKET;        { last sent packet }
		tf_lastlen: integer;    { length of last sent pkt }
		tf_expected: integer;   { most recently processed block }
		tf_fport: integer;      { foreign port }
		tf_task: Ref_task;      { main task for tftp connection }
		tf_tm: Ref_timer;       { our timer }
		tf_state: integer;      { state of connection }
		tf_done:ProcPTR;        { Procedure to call on close }
		tf_tries: integer;      { # of retries already done }
		tf_mode: integer;       { mode := IMAGE, [net]ASCII, ... }
		tf_dir: integer;        { direction of the transfer }
		tf_size: LongInt;       { # of bytes transferred }
		tf_rcv: integer;        { # of packets received }
		tf_snt: integer;        { # of packets sent }
		tf_ous: integer;        { # of out of sequence packets }
		tf_tmo: integer;        { # of timeouts }
		tf_rsnd: integer;       { # of resends }
		tf_trt: LongInt;        { round trip time }
		tf_rt: LongInt;	        { current timeout }
		tf_NR: Byte;	        { number rexmissions of this pkt }
		tf_NR_last: Byte;       { '     '       ' of prev pkt }
		tf_K: Byte;	        { tuning constant }
		tf_SAWCR: Boolean;      { did last packet end in CR? }
		tf_sent: LongInt;       { time that pkt was sent }
		tf_PB: ParmBlkPtr;      { parameter block for I/O }
		tf_fp: tf_FilePart;     { Which part of Macintosh }
		tf_volume: Integer;     { volume identifier of file }
		tf_fn: STR255;	        { name of the file - what a storage waste }
	END;

{ Generic TFTP Packet }

   tfpacket = PACKED RECORD
		tf_op: Integer;	        { op code }
		tf_block: Integer;      { Block of type dependent data }
		END;

   Ref_tftp = ^ tfpacket;

VAR
	ntftps: Byte;
	refusedt: LongInt;      { time of most recent transfer refusal }

FUNCTION tftp_head(p:PACKET):Ref_TFTP;

IMPLEMENTATION

{$S TFTPSeg}

FUNCTION tftp_head(p:PACKET):Ref_TFTP;
BEGIN
    tftp_head := POINTER(ORD4(udp_data(udp_head(in_head(p)))));
END; { tftp_head }

END.

!E!O!F!
#
#
echo extracting net/tftp_file.text...
cat >net/tftp_file.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
{$DECL BUNDLE}
{$SETC BUNDLE := true}
{$DECL ALLOCT}
{$SETC ALLOCT := false}
UNIT TFTP_File;

{ Please note the copyright notice in the file "copyright/notice" }

  { TFTP_LIB module using UDP over the Applebus, file operations }
  { by Tim Maroney (C-MU)   }

INTERFACE

{$L-}
   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-err_lib	  } Err_Lib,
      {$U net-IP_Lib	  } IP_Lib,
      {$U net-UDP_Lib	  } UDP_Lib,
      {$U net-calls	  } Call_Lib,
      {$U net-tftp_defs	  } TFTP_Defs;

{$L+}

FUNCTION ForkZero(cn:Ref_tfconn):BOOLEAN;
procedure CStr2PStr(PC: PTR; PS: StringPtr);
procedure PStr2CStr(PS: StringPtr; PC:Ptr);
PROCEDURE ScratchIt(cn:Ref_tfconn; VAR OSStatus:OSErr);
FUNCTION OpenOK(cn:Ref_tfconn; OSStatus:OSErr; p:PACKET):BOOLEAN;
FUNCTION MacPart(cn:Ref_tfconn; expected:INTEGER):Boolean;
PROCEDURE tfudperr(udpc: UDPCONN; p: PACKET; code:Integer; text: StringPtr);

{$IFC BUNDLE}
PROCEDURE setbundle(fn:StringPtr; vol:integer);
{$ENDC}

IMPLEMENTATION

{$S TFTPSeg }

FUNCTION ForkZero(cn:Ref_tfconn):BOOLEAN;
VAR OSStatus: OSErr;
BEGIN
     OSStatus := PBGetEOF(cn^.tf_PB,FALSE);
     IF OSStatus <> noERR THEN ForkZero := TRUE
     ELSE ForkZero := (ORD4(cn^.tf_PB^.ioMisc) = 0);
END;

procedure CStr2PStr(PC: PTR; PS: StringPtr);
{ Take a pointer to a C string and make it into a Pascal string }
VAR  Size: 0..255;
     ZeroPtr: PTR;
BEGIN
       ZeroPtr := PC;
       FOR Size := 0 TO 255 DO
	  IF ZeroPtr^ = 0 THEN BEGIN { Found end of C string, now copy it }
	       BlockMove(PC,POINTER(ORD4(PS)+1),Size);
	       ZeroPtr := POINTER(ORD4(PS));
	       ZeroPtr^ := Size;
	       EXIT(CStr2PStr);
	       END
	  ELSE ZeroPtr := POINTER(ORD4(ZeroPtr) + 1); { Advance the C ptr }
       { Did not find a null byte !}
       PS^ := '';
END;

procedure PStr2CStr(PS: StringPtr; PC:Ptr);
VAR TempPtr: PTR;
BEGIN
       TempPtr := POINTER(ORD4(PS)+1);
       BlockMove(TempPtr,PC,Length(PS^));
       PC := POINTER(ORD4(PC)+Length(PS^));
       PC^ := 0; { Place a null byte at end of string }
END;

PROCEDURE ScratchIt(cn:Ref_tfconn; VAR OSStatus:OSErr);
BEGIN
	IF OSStatus = NoErr THEN begin
		{ Already there, see if we can delete it }
		OSStatus := PBClose(cn^.tf_PB,FALSE);
		if OSStatus <> noErr then exit(ScratchIt);
		cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
		cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
		cn^.tf_PB^.ioVersNum := 0;
		OSStatus := PBDelete(cn^.tf_PB,FALSE);
		if OSStatus <> noErr then exit(ScratchIt);
		OSStatus := fnfErr
		end;
	IF OSStatus = fnfErr THEN BEGIN
		{ Hm, name not present, let's create it }
		cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
		cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
		cn^.tf_PB^.ioVersNum := 0;
		OSStatus := PBCreate(cn^.tf_PB,FALSE);
		IF OSStatus = noErr THEN BEGIN { Have to set the finder info }
			cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
			cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
			cn^.tf_PB^.ioVersNum := 0;
			cn^.tf_PB^.ioFlCrDat := TickCount;
			cn^.tf_PB^.ioFlMdDat := TickCount;
			cn^.tf_PB^.ioFlFndrInfo.fdCreator := '????';
			cn^.tf_PB^.ioFlFndrInfo.fdFlags := 0;
			cn^.tf_PB^.ioFlFndrInfo.fdFldr := fDisk;
			{ fdLocation is irrelevant; will be inited by Finder }
			if cn^.tf_fp = tf_RsrcPart then
				cn^.tf_PB^.ioFlFndrInfo.fdType := 'APPL'
			else    cn^.tf_PB^.ioFlFndrInfo.fdType := 'TEXT';
			OSStatus := PBSetFInfo(cn^.tf_PB,FALSE);
			END;
		IF OSStatus = noErr THEN BEGIN { Created it, now open it }
			cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
			cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
			cn^.tf_PB^.ioVersNum := 0;
			cn^.tf_PB^.ioPermssn := fsRdWrPerm;
			cn^.tf_PB^.ioMisc := NIL;
			IF cn^.tf_fp = tf_DataPart
			THEN    OSStatus := PBOpen(cn^.tf_PB,FALSE)
			ELSE    OSStatus := PBOpenRF(cn^.tf_PB,FALSE);
		END;
	END; { end of fnfErr}

	{ If everything is still OK, set mark to beginning of file }
	IF OSStatus = noErr THEN BEGIN
		cn^.tf_PB^.ioPosMode := fsFromStart;
		cn^.tf_PB^.ioPosOffset := 0;
		OSStatus := PBSetFPos(cn^.tf_PB,FALSE);
		END;
END; { of Scratch It }

FUNCTION OpenOK(cn:Ref_tfconn; OSStatus:OSErr; p:PACKET):BOOLEAN;
VAR     LongDummy:LONGINT;
BEGIN
	if (OSStatus <> noErr) THEN BEGIN
		FOpenErr(StrCvt(cn^.tf_fn),OSStatus);
		refusedt := TickCount;
{$IFC ALLOCT}
		Write('OpenOK: ');
{$ENDC}
		if p <> NIL then udp_free(p);
		ntftps := ntftps - 1;
		OpenOK := false;
		END
	ELSE    OpenOK := true;
END; { OpenOK }

{ Mac mode transfers involve three file parts.  This is managed by checking
  EOF's in Mac mode.  If there is an EOF and this isn't yet the data part,
  MacPart is called to switch to the next part. }

FUNCTION MacPart(cn:Ref_tfconn; expected:INTEGER):Boolean;
VAR OSStatus:OSErr;
    retval:Boolean;
BEGIN
	cn^.tf_expected := expected;
	if (cn^.tf_fp = tf_RsrcPart) then
		begin
{$IFC DEBUG}
		WriteLn('MacPart: changing to data fork');
{$ENDC}
		{ Open file descriptor with data fork }
		OSStatus := PBClose(cn^.tf_PB,FALSE);
		cn^.tf_fp := tf_DataPart;
		cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
		cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
		cn^.tf_PB^.ioVersNum := 0;
		cn^.tf_PB^.ioMisc := NIL;
		if cn^.tf_dir = GET then
			BEGIN
			cn^.tf_PB^.ioPermssn := fsRdWrPerm;
			OSStatus := PBOpen(cn^.tf_PB,FALSE);
			END
		ELSE
			BEGIN
			cn^.tf_PB^.ioPermssn := fsRdPerm;
			OSStatus := PBOpen(cn^.tf_PB,FALSE);
			IF OSStatus = noErr THEN BEGIN
				cn^.tf_PB^.ioPosMode := fsFromStart;
				cn^.tf_PB^.ioPosOffset := 0;
				OSStatus := PBSetFPos(cn^.tf_PB,FALSE);
				END;
			END;
		retval := OpenOK(cn,OSStatus,NIL);
		if not retval then tfudperr(cn^.tf_udp, cn^.tf_outp, FNOTFOUND,
			 StrCvt('Could not change to data fork'));
		MacPart := retval;
		end

	else if (cn^.tf_fp = tf_FindPart) then
		begin
		{ Open file descriptor with resource file }
{$IFC DEBUG}
		WriteLn('MacPart: changing to resource fork');
{$ENDC}
		cn^.tf_fp := tf_RsrcPart;
		cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
		cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
		cn^.tf_PB^.ioVersNum := 0;
		cn^.tf_PB^.ioMisc := NIL;
		if cn^.tf_dir = GET then
			BEGIN
			cn^.tf_PB^.ioPermssn := fsRdWrPerm;
			OSStatus := PBOpenRF(cn^.tf_PB, FALSE);
			END
		else
			BEGIN
			cn^.tf_PB^.ioPermssn := fsRdPerm;
			OSStatus := PBOpenRF(cn^.tf_PB, FALSE);
			IF OSStatus = noErr THEN BEGIN
				cn^.tf_PB^.ioPosMode := fsFromStart;
				cn^.tf_PB^.ioPosOffset := 0;
				OSStatus := PBSetFPos(cn^.tf_PB,FALSE);
				END;
			END;
		retval := OpenOK(cn,OSStatus,NIL);
		if not retval then tfudperr(cn^.tf_udp, cn^.tf_outp, FNOTFOUND,
			 StrCvt('Could not change to resource fork'));
		MacPart := retval;
		end;
END;

{$IFC BUNDLE}
{ If this file has a bundle in its resource fork, then set the bundle bit }

PROCEDURE setbundle(fn:StringPtr; vol:integer);
VAR     info:FInfo;
	refnum,count1,count2:integer;
	h:handle;
	o:^OSType;
begin
{$IFC DEBUG}
	WriteLn('setbundle: called on ',fn^,', volume ',vol:1);
{$ENDC}
	count1 := CountResources('BNDL');
	if setvol(NIL,vol) <> noErr then exit(setbundle);
	refnum := OpenResFile(fn^);
	if refnum = -1 then exit(setbundle);
	count2 := CountResources('BNDL');
	if count2 > count1 then { there's a bundle in this file }
		begin
		h := GetIndResource('BNDL',1);
		if (h = NIL) | (GetFInfo(fn^,vol,info) <> noErr) then begin
			CloseResFile(refnum);
			exit(setbundle);
			end;
		o := POINTER(ORD4(h^));
		info.fdCreator := o^;
		info.fdFlags := BitOR(info.fdFlags,fHasBundle);
		if SetFInfo(fn^,vol,info) <> noErr then begin
			CloseResFile(refnum);
			exit(setbundle);
			end;
		{ TODO: Open the desk top and remove the version data resource }
		end;
	CloseResFile(refnum);
end;
{$ENDC}

PROCEDURE tfudperr(udpc: UDPCONN; p: PACKET; code:Integer; text: StringPtr);
{ error packet definitions }
CONST
     tf_err_offset = 4;
TYPE
     tferr = PACKED RECORD
	{ +0 }	      tf_op: integer;	      { would be 5 }
	{ +2 }	      tf_code: integer;
	{ +4 }	      tf_err: packed array[0:0] of byte;
	END;
     Ref_tferr = ^ tferr;
VAR
	len: integer;
	perr: ^tferr;
	dummy: integer;
	FakeStr: StringPtr;
BEGIN
	len := 4;

	perr := { (struct tferr *) } POINTER(ORD4(tftp_head(p)));
	perr^.tf_op := { bswap} { No byte swap on 68000 }  (ERRPCK);
	perr^.tf_code := { bswap } { No bte swap on 68000 } (code);

	if(code=ERRTXT) THEN BEGIN
		FakeStr := Text;
		PStr2CStr( text,
			   { @perr^.tf_err } POINTER(ORD4(perr) + tf_err_offset));
		len := len + length(text^)+1; { 1 for the null byte }
		END
	else BEGIN
		CASE code OF
		    0: FakeStr := StrCvt('The JNC Memorial BUGHALT');
		    1: FakeStr := StrCvt('File not found');
		    2: FakeStr := StrCvt('Access violation');
		    3: FakeStr := StrCvt('Disk full');
		    4: FakeStr := StrCvt('Illegal TFTP operation');
		    5: FakeStr := StrCvt('Unknown transfer ID');
		    6: FakeStr := StrCvt('File already exists');
		    7: FakeStr := StrCvt('No such user');
		    otherwise FakeStr := StrCvt('Unknown error');
		END; { end of CASE }
		PStr2CStr( FakeStr,
			   { @perr^.tf_err } POINTER(ORD4(perr) + tf_err_offset));
		len := len + length(FakeStr^)+1; { 1 for the null byte }
		END;
{$IFC DEBUG}
	 WriteLn('TFTP: Sending error packet, ',code,' <',FakeStr^,'>');
{$ENDC}
	 dummy := udp_write(udpc, p, len);
END; { End of tfudperr }

END.

!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting net/timer_lib.text...
cat >net/timer_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
UNIT Timer_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

{ This file contains the declarations for the timer management package. }

INTERFACE

{$L-}

   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf;

{$L+}

CONST   TPS	 = 60;		        { Clock ticks per second }
	TIMERHIWATER    = 30;	        { number of free timers to keep }

TYPE

	Ref_timer = ^ timer;

	{ This is an extended version of the Mac vertical retrace queue format }
	timer = PACKED RECORD	        { a timer }
		tm_qLink:Ref_timer;     { next element in vertical retrace queue }
		tm_qType:INTEGER;       { will be ORD(vType) }
		tm_vblAddr:ProcPtr;     { points to tm_PSHARG, below }
		tm_vblCount:INTEGER;    { timer frequency }
		tm_vblPhase:INTEGER;    { timer phase }
		tm_tmLink:Ref_timer;    { secondary link, for appl. timers only }
		{ The following is 68000 machine code }
		tm_PSHA5:INTEGER;       { MOVE.L A5,-(SP) }
		tm_LEAA0:INTEGER;       { LEA [word immediate],A0 }
		tm_CURA5:INTEGER;       { system global CurrentA5 (word addr) }
		tm_GETA5:INTEGER;       { MOVE.L (A0),A5 }
		tm_PSHARG:INTEGER;      { MOVE.L [long immediate],-(SP) }
		tm_arg:Ptr;	        { argument to pass to subroutine }
		tm_JSR:INTEGER;	        { JSR [long immediate] }
		tm_subr: ProcPtr;       { timer subroutine to call }
		tm_POPA5:INTEGER;       { MOVE.L (SP)+,A5 }
		tm_RTS:INTEGER	        { RTS }
		END;

	time_q = packed record	        { the queue of free timers }
		qFlags: Integer;
		qHead: Ref_timer;
		qTail: Ref_timer
		end;

{ This file contains the routines which make up the routines for setting
 * and clearing timers.
 * The following routines are included in this package:
 *      tm_set	        set a timer to fire after number of seconds
 *      tm_mset	        set a timer, argument in milliseconds
 *      tm_tset	        set a timer, argument in clock ticks
 *      tm_reset        reset a timer to go off at a different time
 *      tm_clear        clear a previously set timer
 *      tm_main	        the main routine of the timer task
 *      tm_init	        init the timer system
 }
{  Addition of tm_tset and tm_mset, 12/83. <J.H. Saltzer>  }

VAR     TIMERDEBUG: Boolean;

PROCEDURE tm_set(nsecs:LongInt; subr:PROCPTR; arg:PTR; tm: Ref_Timer);
PROCEDURE tm_mset(msecs:LongInt; subr:PROCPTR; arg:PTR; tm:Ref_Timer);
PROCEDURE tm_tset (nticks:LongInt; subr:PROCPTR; arg:PTR; tm:Ref_Timer);
FUNCTION tm_reset (nsecs: LongInt; tm: Ref_Timer): Boolean;
FUNCTION tm_clear(tm: Ref_Timer): Boolean;
PROCEDURE tm_init;
FUNCTION tm_alloc:Ref_Timer;
FUNCTION tm_free(t: Ref_Timer): Boolean;
PROCEDURE tm_allFree;

IMPLEMENTATION

CONST
	RTS = $4E75;		        { instruction format of 68000 RTS }
	PSHARG = $2F3C;		        { MOVE.L [next longword],-(SP) }
	JSR = $4EB9;		        { JSR [next longword] }
	PSHA5 = $2F0D;		        { MOVE.L A%,-(SP) }
	LEAA0 = $41F8;		        { LEA [next word],A0 }
	CURA5 = $0904;		        { location of system global CurrentA5 }
	POPA5 = $2A5F;		        { MOVE.L (SP)+,A5 }
	GETA5 = $2A50;		        { MOVE.L (A0),A5 }

{ Internal variables }

VAR
	freetmq: time_q;	        { queue of free timers }
	allTimers:Ref_timer;	        { list of timers allocated }

{ Initialize the timer package. }

{$S InitSeg }

PROCEDURE tm_init;
BEGIN
	TIMERDEBUG := false;
	freetmq.qFlags := 0;	        { queue of free timers }
	freetmq.qHead := NIL;
	freetmq.qTail := NIL;
	allTimers := NIL;
END; { tm_init }

{$S	   }

{ Set a timer to go off after nticks clock ticks.  When the timer goes
 * off, call the specified subroutine with the specified argument.
 * This routine runs in the context of the caller's task;
 * it just enqueues the timer.
 }

PROCEDURE tm_tset(nticks:LongInt;       { timer expiration time }
		  subr:PROCPTR;	        { subroutine to call on expiration }
		  arg:PTR;	        { arg to pass to subr. }
		  tm:Ref_Timer);        { place to return timer id }
VAR     Dummy: OSErr;
BEGIN

{$IFC DEBUG}
	IF TIMERDEBUG THEN BEGIN
		WriteLn('TM_SET: setting timer ',ORD4(tm),
			' for ',nticks,' ticks.');
		END;
{$ENDC}

	{  make sure not already queued. }

	Dummy := VRemove(POINTER(ORD4(tm)));
	tm^.tm_qLink := NIL;		        { no next element }
	tm^.tm_vblCount := nticks;	        { timer frequency }
	tm^.tm_vblPhase := 0;		        { timer phase }
	tm^.tm_vblAddr := POINTER(ORD4(tm)+18); { addr of procedure }
	tm^.tm_PSHA5 := PSHA5;
	tm^.tm_LEAA0 := LEAA0;
	tm^.tm_CURA5 := CURA5;
	tm^.tm_GETA5 := GETA5;
	tm^.tm_PSHARG := PSHARG;
	tm^.tm_arg := arg;		        { argument to pass }
	tm^.tm_JSR := JSR;
	tm^.tm_subr := subr;		        { subroutine to call }
	tm^.tm_POPA5 := POPA5;
	tm^.tm_RTS := RTS;

	Dummy := VInstall(POINTER(ORD4(tm)));
END;    {  end of tm_tset()  }


{ Reset a (running) timer to go off in nsecs seconds
 * instead of at the time it is currently set for.  If in fact the
 * timer is not already set, return FALSE; otherwise return TRUE.
 * Does not modify the upcall in the timer.
 }

FUNCTION tm_reset(nsecs: LongInt; tm: Ref_Timer): Boolean;
VAR     expired: Boolean;
BEGIN
	expired := (tm^.tm_vblCount = 0);
	{ if NOT expired THEN
		expired := (VRemove(POINTER(ORD4(tm))) <> noErr); }
	if expired THEN BEGIN
{$IFC DEBUG}
		IF TIMERDEBUG THEN
			WriteLn('TIMER_RESET: timer already expired.');
{$ENDC}
		tm_reset := False;
		exit(tm_reset);	   { timer expired, give up }
		END;

{$IFC DEBUG}
	IF TIMERDEBUG THEN BEGIN
		WriteLn('TIMER_RESET: timer reset for ',nsecs,' seconds.');
		END;
{$ENDC}

	tm^.tm_qLink := NIL;		        { no next element }
	tm^.tm_vblCount := nsecs*TPS;	        { timer expiration time }
	tm_reset := (VInstall(POINTER(ORD4(tm))) = noErr);
END;    {  end of tm_reset()  }

{ set timer in seconds  }

PROCEDURE tm_set(nsecs:LongInt; subr:PROCPTR; arg:PTR; tm: Ref_Timer);
BEGIN
	tm_tset(nsecs*TPS, subr, arg, tm);
END; { end of tm_set }

{ set timer in milliseconds  }

PROCEDURE tm_mset(msecs:LongInt; subr:PROCPTR; arg:PTR; tm:Ref_Timer);
BEGIN
	tm_tset (((msecs*TPS) DIV 1000), subr, arg, tm) ;
END; { end of tm_mset }

{ Clear the timer specified by the passed timer identifier.  The timer
 * identifier gives a pointer to the timer to be cleared.
 * Free the timer's storage
 * (into the free list up to TIMERHIWATER elements).
 * Returns FALSE if the specified timer was not found in the queue,
 * TRUE otherwise.
 }

FUNCTION tm_clear(tm: Ref_Timer): Boolean;
BEGIN
	IF (tm^.tm_vblCount = 0) THEN BEGIN
{$IFC DEBUG}
		IF TIMERDEBUG THEN BEGIN
			WriteLn('TIMERCLEAR: timer ',ORD4(tm),' already expired.');
			END;
{$ENDC}
		tm_clear := FALSE;
		exit(tm_clear);
		END;

{$IFC DEBUG}
	IF TIMERDEBUG THEN BEGIN
		WriteLn('TIMERCLEAR: clearing timer ',ORD4(tm));
		END;
{$ENDC}

	{ tm^.tm_vblCount := 0; }
	if VRemove(POINTER(ORD4(tm))) <> noErr THEN tm_clear := FALSE
	else tm_clear := TRUE;
END;    {  end of tm_clear();  }

{ Allocate a timer and return a pointer to it }

FUNCTION tm_alloc:Ref_Timer;
VAR     t: Ref_timer;
	sz:Size;
	Dummy:OSErr;
BEGIN
	t := POINTER(ORD4(freetmq.qHead));
	if t <> NIL then
		Dummy := dequeue(POINTER(ORD4(t)),@freetmq)
	else
		BEGIN
		sz := sizeof(timer);
		t :=  {(timer *)} POINTER(ORD4(NewPtr(sz)));
		if t = NIL THEN BEGIN
			tm_alloc := NIL;
			exit(tm_alloc);
			END;
		END;

	t^.tm_qLink := NIL;
	t^.tm_qType := ORD(vType);
	t^.tm_vblCount := 0;
	t^.tm_tmLink := allTimers;
	allTimers := t;
	tm_alloc := t;
END;

{ Free up a timer. Returns true if successful, false otherwise }

FUNCTION tm_free(t: Ref_Timer): Boolean;
VAR
	tmp:Ref_timer;
	l:Integer;
	Dummy:OSErr;
	flag:Boolean;
BEGIN
	{ Check if the timer is enqueued }

	if VRemove(POINTER(ORD4(t))) = noErr THEN
		begin
{$IFC DEBUG}
		WriteLn('Tried to free active timer.');
{$ENDC}
		Dummy := VInstall(POINTER(ORD4(t)));
		tm_free := false;
		exit(tm_free);
		end;

	{ Have to remove it from allTimers }
	if allTimers = t then
		allTimers := t^.tm_tmLink
	else
		begin
		tmp := allTimers;
		flag := true;
		while flag and (tmp^.tm_tmLink <> NIL) do
			begin
			if tmp^.tm_tmLink = t then
				begin
				tmp^.tm_tmLink := t^.tm_tmLink;
				flag := false;
				end
			else
				tmp := tmp^.tm_tmLink;
			end;
		end;

	l := 0;
	tmp := freetmq.qHead;
	while tmp <> NIL do
		begin
		l := l+1;
		tmp := tmp^.tm_qLink;
		end;

	if l < TIMERHIWATER THEN enqueue(POINTER(ORD4(t)),@freetmq)
	else DisposPtr(POINTER(ORD4(t)));

	tm_free := TRUE;
END; { tm_free }

{ Free all timers from the vertical retrace queue -- done at application death }

PROCEDURE tm_allFree;
VAR
	Dummy:OSErr;
	tm:Ref_timer;
BEGIN
	tm := allTimers;
	while tm <> NIL do
		begin
		Dummy := VRemove(POINTER(ORD4(tm)));{ doesn't matter if it's there or not }
		tm := tm^.tm_tmLink;
		end;
	allTimers := NIL;
END;

END. { End of Timer Library }

!E!O!F!
#
#
echo extracting net/tn_lib.text...
cat >net/tn_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
{$DECL LISTEN}
{$SETC LISTEN := true}
UNIT TN_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

INTERFACE

{$L-}
   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-err_lib	  } Err_Lib,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-ip_lib	  } IP_Lib,
      {$U net-tcp_lib	  } TCP_Lib,
      {$U net-udp_lib	  } UDP_Lib,
      {$U net-name_user	  } Name_User,
      {$U net-tftp_defs	  } TFTP_Defs,
      {$U net-tftp_lib	  } TFTP_Lib,
      {$U net-calls	  } Call_Lib,
      {$U net-name_host	  } NameHost,
      {$U net-term_lib	  } Term_Lib;
{$L+}

{ Definitions for telnet }

CONST   TNTKSZ = 3072;

TYPE
ucb = record
	u_state:Integer;        { XESTAB or XCLOSING }
	u_tcpfull:Boolean;      { is tcp's output buffer full? }
	u_tftp:Boolean;	        { is tftp running passively? }
	u_rstate:char;	        { Read terminal state }
				{ NORMALMODE }
				{ BLOCK - don't read terminal }
	u_rspecial:char;        { Read terminal character handling }
				{ NORMALMODE }
				{ TCPFULL - tcp output buffer full }
	u_wstate:char;	        { Write terminal state }
				{ NORMALMODE }
				{ URGENTM - urgent mode }
				{ ignore nonspecial chars }
	u_wspecial:char;        { Write terminal character handling }
				{ NORMALMODE }
				{ RETURN (13), IAC, WILL, WONT, DO, DONT -
				   processing special chars }
	u_sendm:char;	        { Send mode }
				{ EVERYC - send to net on every char }
				{ NEWLINE - send to net on newline }
	u_echom:char;	        { Echo mode }
				{ LOCAL - local echo }
				{ REMOTE - remote echo }
	u_echongo:char;	        { Echo negotiation request outstanding }
				{ NORMALMODE }
				{ LECHOREQ - IAC DONT ECHO was sent }
				{ RECHOREQ - IAC DO ECHO was sent }
	u_echoback:Boolean;     { whether to echo all received chars back
				  to the user }
	u_tcconn:Ref_tcconn;    { the TCP connection associated with the TELNET }
	u_tnshost:STR255;       { name of remote host }
end;

ucbptr = ^ucb;

PROCEDURE TelnetMenuCmd(item:Integer; theMenu:MenuHandle);
PROCEDURE TNFixMenu(theMenu:MenuHandle);
PROCEDURE tel_init;
PROCEDURE gt_usr(c:char);
PROCEDURE showstats;
FUNCTION tn_conn(var s:STR255; fsock:Integer):ucbptr;
FUNCTION tntftp(fhost:in_name; var filename:STR255; dir:unsigned):Integer;
PROCEDURE tntfdn(success:Integer);
PROCEDURE SendMenuCmd(item:Integer; theMenu:MenuHandle);
PROCEDURE FixSendMenu(theMenu:menuHandle);

VAR     tn_done:Boolean;
	tn_tftp:Boolean;        { whether or not to do the TFTP server }

IMPLEMENTATION

VAR TheName:STR255;
    TheFSock:Integer;
    ucb_extern:ucb;
    MyDialog:DialogPtr;
    ServerRunning: Boolean;
    nm_task:Ref_task;
    pucb:ucbptr;

CONST
 tntfId = 11;

 NORMALMODE = chr(0);
{
 SPECIAL = chr(1);
 TEST = chr(2);
 CONFIRM = chr(3);
}
 HOLD = chr(4);

 BLOCK = chr(1);
 NOBLOCK = chr(2);
 URGENTM = chr(1);
 EVERYC = chr(1);
 NEWLINE = chr(2);
 LOCAL = chr(1);
 REMOTE = chr(2);
 LECHOREQ = chr(1);
 RECHOREQ = chr(2);

 NOP = chr(241);
 IAC = chr(255);
 WILL = chr(251);
 WONT = chr(252);
 DOIT = chr(253);
 DONT = chr(254);
 DM = chr(242);
 INTP = chr(244);
 AO = chr(245);
 AYT = chr(246);
 GA = chr(249);
 OPTECHO = chr(1);
 OPTSPGA = chr(3);
 OPTTMARK = chr(6);

 RETURN = chr($d);
 LINEFEED = chr($a);

 XESTAB = 1;
 XCLOSING = 2;
 XCLOSED  = 3;

 TELNETSOCK = 23;  { Telnet well known socket no. }
 C_L = chr($c);	   { Control-L }

PROCEDURE echolocal(pucb:ucbptr); forward;
PROCEDURE echoremote(pucb:ucbptr); forward;
PROCEDURE ttechoremote(pucb:ucbptr); forward;
PROCEDURE ttecholocal(pucb:ucbptr); forward;
PROCEDURE tcpfull; forward;

FUNCTION tn_alloc:ucbptr;
BEGIN
	pucb := @ucb_extern;
	pucb^.u_state := XESTAB;
	pucb^.u_tcpfull := false;
	pucb^.u_tftp := false;
	pucb^.u_rstate := NORMALMODE;
	pucb^.u_rspecial := NORMALMODE;
	pucb^.u_wstate := NORMALMODE;
	pucb^.u_wspecial := NORMALMODE;
	pucb^.u_echoback := false;
	pucb^.u_echom := LOCAL;
	pucb^.u_echongo := NORMALMODE;
	pucb^.u_tcconn := NIL;
	pucb^.u_tnshost := 'Unknown';
	pucb^.u_sendm := EVERYC;
	tn_alloc := pucb;
END;

{$S InitSeg}

PROCEDURE tel_init;
begin
	pucb := NIL;
	ServerRunning := false;
	nm_task := NIL;
	em_init;
end;

{$S	    }

PROCEDURE tn_free;
begin
	if tn_tftp AND pucb^.u_tftp then
		begin
		{ File transfer service turned off. }
		tfs_off;
		pucb^.u_tftp := FALSE;
		end;
	{ Don't really throw away connection storage }
	pucb := NIL;
end;

{ Return true if telnet must run; false otherwise. }

FUNCTION mst_run:Boolean;
begin
	mst_run := TRUE;
end;

PROCEDURE bfr;
begin
	ucb_extern.u_tcpfull := false;
end;

{ gt_usr  Process a char from user's terminal }

PROCEDURE gt_usr(c:char);
VAR
	i:Integer;
	ich:Integer;
	dummy:Boolean;
begin
	if pucb = NIL then exit(gt_usr);

	if (pucb^.u_rspecial <> HOLD)
	   AND (pucb^.u_tcconn^.conn_state = ESTAB) then
		begin
		case (pucb^.u_rspecial) of
		NORMALMODE:
			begin
			if(pucb^.u_tcpfull) then
				begin
				tcpfull;
				exit(gt_usr);
				end;

			if(pucb^.u_echom = LOCAL) then
				begin
				em(c);
				if c = RETURN then em(LINEFEED);
				end;

			if c = RETURN then
				c := LINEFEED
			else if c = LINEFEED then
				dummy := tc_put(pucb^.u_tcconn,RETURN)
			else if c = IAC then
				dummy := tc_put(pucb^.u_tcconn,IAC);

			if(pucb^.u_sendm = EVERYC) then
				begin
				if tc_fput(pucb^.u_tcconn,c) then tcpfull;
				end
			else begin
				if tc_put(pucb^.u_tcconn,c) then
					begin
					tcpfull;
					exit(gt_usr);
					end;
				if (c = LINEFEED) then
					tcp_ex(pucb^.u_tcconn);
				end;
			end;

{$IFC DEBUG}
		otherwise
			begin
			WriteLn('Telnet BUG ',ord(pucb^.u_rspecial):1);
			pucb^.u_rspecial := NORMALMODE;
			end;
{$ENDC}
		end;
	end; { of if }
end; { of gt_usr }

{ wr_usr  manage chars coming from net and going to user
 Process received telnet special chars and option negotiation.
 When wstate is URGENTM, only process special chars. }

PROCEDURE wr_usr(c:char);
VAR
	dummy:Boolean;
	theEvent:EventRecord;   { used to get keyboard chars for DO TIMING MARK }
BEGIN
	if pucb = NIL then exit(wr_usr);

	case (pucb^.u_wspecial) of
	NORMALMODE:
		begin
		if (c = IAC) then
			pucb^.u_wspecial := IAC
		else
			{ Don't print ^L because Multics sends them
			  quite often }
			if(pucb^.u_wstate <> URGENTM) AND (c <> C_L) then
				begin
				em(c);
				if pucb^.u_echoback then
					begin
					dummy := tc_put(pucb^.u_tcconn,c);
					if c = RETURN then
						dummy :=
						  tc_put(pucb^.u_tcconn,LINEFEED);
					if pucb^.u_sendm = EVERYC then
						tcp_ex(pucb^.u_tcconn);
					end;
				end;
			end;

	IAC:
		case (c) of
		IAC:
			begin
			if(pucb^.u_wstate <> URGENTM) then
				em(c);
			pucb^.u_wspecial := NORMALMODE
			end;

		AO:
			begin
			dummy := tc_put(pucb^.u_tcconn,IAC);
			dummy := tc_put(pucb^.u_tcconn,DM);
			tcpurgent(pucb^.u_tcconn);
			pucb^.u_wspecial := NORMALMODE
			end;

	       WILL,WONT,DOIT,DONT:
			pucb^.u_wspecial := c;

	       GA:
			begin
			pucb^.u_wspecial := NORMALMODE;
			emstr('*GA*');
			end;

	       NOP:     begin
			pucb^.u_wspecial := NORMALMODE;
			end;

		otherwise
			{ Ignore IAC x }
			pucb^.u_wspecial := NORMALMODE;
		end; { case c }

	WILL:
		begin
		case c of
		OPTECHO:
			begin
			case (pucb^.u_echongo) of
			NORMALMODE:
				begin
				{ This host did not initiate echo negot,
				  so respond }
				if(pucb^.u_echom <> REMOTE) then
					echoremote(pucb);
				end;
			LECHOREQ:
				{ Rejecting my IAC DONT ECHO (illegit) }
				ttechoremote(pucb);
			RECHOREQ: { Everything is OK } ;
			end; { case pucb^.u_echongo }

			pucb^.u_echongo := NORMALMODE;
			end;

		OPTSPGA:
			{ suppress GA's }
			begin
			dummy := tc_put(pucb^.u_tcconn,IAC);
			dummy := tc_put(pucb^.u_tcconn,DOIT);
			dummy := tc_fput(pucb^.u_tcconn,c);
			end;

		otherwise
			begin
			dummy := tc_put(pucb^.u_tcconn,IAC);
			dummy := tc_put(pucb^.u_tcconn,DONT);
			dummy := tc_fput(pucb^.u_tcconn,c);
			end;
		end; { case c }

		pucb^.u_wspecial := NORMALMODE;
		end;

	WONT:
		begin
		if c = OPTECHO then
			begin
			case (pucb^.u_echongo) of
			NORMALMODE:
				{ This host did not initiate echo negot,
				  so respond }
				if pucb^.u_echom <> LOCAL then
					echolocal(pucb);
			RECHOREQ:
				{ Rejecting my IAC DOIT ECHO }
				ttecholocal(pucb);
			LECHOREQ: { Everything is OK } ;
			end; { case pucb^.echongo }

			pucb^.u_echongo := NORMALMODE;
			end;
		pucb^.u_wspecial := NORMALMODE;
		end;

	DOIT:
		begin
		if c = OPTECHO then
			begin
			pucb^.u_echoback := true;
			dummy := tc_put(pucb^.u_tcconn,IAC);
			dummy := tc_put(pucb^.u_tcconn,WILL);
			dummy := tc_fput(pucb^.u_tcconn,OPTECHO);
			end
		else if c = OPTTMARK then begin
			em_flush;
			while GetNextEvent(keyDownMask+autoKeyMask,theEvent) do
				gt_usr(chr(theEvent.message MOD 256));
			dummy := tc_put(pucb^.u_tcconn,IAC);
			dummy := tc_put(pucb^.u_tcconn,WILL);
			dummy := tc_fput(pucb^.u_tcconn,OPTTMARK);
			end
		else
			begin
			dummy := tc_put(pucb^.u_tcconn,IAC);
			dummy := tc_put(pucb^.u_tcconn,WONT);
			dummy := tc_fput(pucb^.u_tcconn,c);
			end;
		pucb^.u_wspecial := NORMALMODE;
		end;

	DONT:
		begin
		if c = OPTECHO then
			begin
			pucb^.u_echoback := false;
			dummy := tc_put(pucb^.u_tcconn,IAC);
			dummy := tc_put(pucb^.u_tcconn,WONT);
			dummy := tc_fput(pucb^.u_tcconn,OPTECHO);
			end;
		pucb^.u_wspecial := NORMALMODE;
		end;

	end; { case pucb^.u_wspecial }
end; { of wr_usr }

PROCEDURE echolocal(pucb:ucbptr);
VAR     dummy:Boolean;
begin
	dummy := tc_put(pucb^.u_tcconn,IAC);
	dummy := tc_put(pucb^.u_tcconn,DONT);
	dummy := tc_fput(pucb^.u_tcconn,OPTECHO);
	pucb^.u_echom := LOCAL;
end;

PROCEDURE ttecholocal(pucb:ucbptr);
begin
	pucb^.u_echom := LOCAL;
end;

PROCEDURE echoremote(pucb:ucbptr);
VAR     dummy:Boolean;
begin
	dummy := tc_put(pucb^.u_tcconn,IAC);
	dummy := tc_put(pucb^.u_tcconn,DOIT);
	dummy := tc_fput(pucb^.u_tcconn,OPTECHO);
	pucb^.u_echom := REMOTE;
end;

PROCEDURE ttechoremote(pucb:ucbptr);
begin
	pucb^.u_echom := REMOTE;
end;

PROCEDURE opn_usr;
VAR pucb:ucbptr;
begin
	EmLn('Open');
	pucb := @ucb_extern;
	echoremote(pucb);
	pucb^.u_echongo := RECHOREQ;
end;

PROCEDURE opn_host;
BEGIN
	EmLn('Open');
END;

PROCEDURE cls_usr;
begin
	EmLn('Closed');
	tn_free;
end;

PROCEDURE tmo_usr;
begin
	NotResponding(StrCvt('TELNET'));
end;

PROCEDURE pr_dot;
begin
	em('.');
end;

{ Functions that interface with the TFTP server }

{ function called when file transfer is requested }

FUNCTION tntftp(fhost:in_name; var filename:STR255; dir:unsigned):Integer;
VAR
	ItemType:Integer;
	ItemHndl:Handle;
	ItemBox:Rect;
	itemHit:Integer;
	dowhat:string[7];
	tntfDialog:DialogPtr;
begin
	cvt_inaddr(fhost,Msg);
	if dir = PUT then dowhat := 'get' else dowhat := 'put';
	ParamText(Msg,dowhat,filename,'');
	tntfDialog := GetNewDialog(tntfId,NIL,POINTER(-1));
	ModalDialog(NIL, itemHit);
	DisposDialog(tntfDialog);
	if itemHit = 1 then tntftp := 1 else tntftp := 0;
end;

{  function called when file transfer is done.  }

PROCEDURE tntfdn(success:Integer);
begin
	Msg := 'TELNET TFTP Server: File transfer ';
	if success = 1 then
		insert('succeeded.',Msg,length(Msg)+1)
	else
		insert('failed.',Msg,length(Msg)+1);
	EmLn(Msg);
end;

{ procedures to handle the two user menus unique to telnet }

PROCEDURE TelnetMenuCmd(item:Integer; theMenu:MenuHandle);
VAR
	dummy:Boolean;
	TempText:STR255;
begin
	case (item) of

	1:      begin
		if pucb = NIL then
			begin
			if not NameRemoteHost(TempText) then
				exit(TelnetMenuCmd);
			em_reset;
			if tn_conn(TempText,TELNETSOCK) = NIL then
				begin
				CantConnect(StrCvt('TELNET'),@TempText);
				end
			else
				begin
				if tn_tftp then
					begin
					if NOT ServerRunning then
						BEGIN
						tfsinit(@tntftp,@tntfdn);
						ServerRunning := true;
						END;
					tfs_on;
					pucb^.u_tftp := TRUE;
					end;
				end;
			end;
		end;

	2:      begin
		if (pucb^.u_tcconn^.conn_state = ESTAB) then
			begin
			tcp_close(pucb^.u_tcconn);
			pucb^.u_state := XCLOSING;
			pucb^.u_rstate := BLOCK;
			end
		else if (pucb^.u_tcconn^.conn_state = LISTEN)
			or (pucb^.u_tcconn^.conn_state = SYNSENT)
			or (pucb^.u_tcconn^.conn_state = SYNRCVD) THEN
			begin
			tcp_close(pucb^.u_tcconn);
			end
		end;

	3:      begin
		if pucb^.u_sendm = EVERYC then
			pucb^.u_sendm := NEWLINE
		else
			pucb^.u_sendm := EVERYC;
		end;

	4:      begin
		if (pucb^.u_tftp) then
			begin
			{ File transfer service turned off. }
			tfs_off;
			pucb^.u_tftp := FALSE;
			end
		else
			begin
			{ File transfer service turned on. }
			tfs_on;
			pucb^.u_tftp := TRUE;
			end;
		end;

{$IFC LISTEN}
	5:      begin
		pucb := tn_alloc;
		em_reset;
		pucb^.u_tcconn :=
			tcp_listen(23, TCPWINDOW, TCPLOWIND,
			@opn_host,@wr_usr,@mst_run,@cls_usr,@tmo_usr,@pr_dot,@bfr);
		pucb^.u_echoback := false;
		pucb^.u_echom := LOCAL;
		if tn_tftp then
			begin
			if Not ServerRunning then
				BEGIN
				tfsinit(@tntftp,@tntfdn);
				ServerRunning := true;
				END;
			tfs_on;
			pucb^.u_tftp := TRUE;
			end;
		end;
{$ENDC}

	6:      begin
		if pucb <> NIL then tn_free;
		tn_done := TRUE;
		end;

	end;    { of item cases }
end; { of TelnetMenuCmd }

PROCEDURE TNFixMenu(theMenu:MenuHandle);
BEGIN
	if (pucb <> NIL) then
		begin
		DisableItem(theMenu,1);
		if (pucb^.u_tcconn^.conn_state = ESTAB)
			or (pucb^.u_tcconn^.conn_state = LISTEN)
			or (pucb^.u_tcconn^.conn_state = SYNSENT)
			or (pucb^.u_tcconn^.conn_state = SYNRCVD) THEN
			EnableItem(theMenu,2)
		else
			DisableItem(theMenu,2);
{$IFC LISTEN}
		CheckItem(theMenu,5,pucb^.u_tcconn^.conn_state=LISTEN);
		DisableItem(theMenu,5);
{$ENDC}
		if (pucb^.u_tcconn^.conn_state = ESTAB) then
			begin
			CheckItem(theMenu,3,pucb^.u_sendm=EVERYC);
			CheckItem(theMenu,4,pucb^.u_tftp);
			EnableItem(theMenu,3);
			if tn_tftp then EnableItem(theMenu,4);
			DisableItem(theMenu,6)
			end
		else    EnableItem(theMenu,6);
		end
	 else
		begin
		CheckItem(theMenu,3,false);
		CheckItem(theMenu,4,false);
		EnableItem(theMenu,1);
		DisableItem(theMenu,2);
		DisableItem(theMenu,3);
		DisableItem(theMenu,4);
{$IFC LISTEN}
		CheckItem(theMenu,5,false);
		EnableItem(theMenu,5);
{$ENDC}
		EnableItem(theMenu,6);
		end;
END;

PROCEDURE SendMenuCmd(item:Integer; theMenu:MenuHandle);
VAR
	dummy:Boolean;
	i:INTEGER;
begin
	if NOT (pucb^.u_tcconn^.conn_state = ESTAB) then exit(SendMenuCmd);
	case item of
	1:      begin
		if(pucb^.u_echom = REMOTE) then
			begin
			echolocal(pucb);
			pucb^.u_echongo := LECHOREQ;
			end
		else
			begin
			{ Remote echo mode. }
			echoremote(pucb);
			pucb^.u_echongo := RECHOREQ;
			end;
		end;

	2:      begin
		{ Sending Are You There. }
		if tc_put(pucb^.u_tcconn,IAC) then
			tcpfull
		else if tc_fput(pucb^.u_tcconn,AYT) then
			tcpfull;
		end;

	3:      begin
		{ Sending abort output. }
		dummy := tc_put(pucb^.u_tcconn,IAC);
		dummy := tc_fput(pucb^.u_tcconn,AO);
		end;

	4:      begin
		{ Sending break. }
		dummy := tc_put(pucb^.u_tcconn,IAC);
		dummy := tc_put(pucb^.u_tcconn,INTP);
		dummy := tc_put(pucb^.u_tcconn,IAC);
		dummy := tc_put(pucb^.u_tcconn,DM);
		tcpurgent(pucb^.u_tcconn);
		end;

	5:      begin
		{ Expediting data. }
		tcp_ex(pucb^.u_tcconn);
		end;
(*
	6:      begin
		{ Send Internet address }
		cvt_inaddr(in_mymach(0),Msg);
		for i := 1 to length(Msg) do gt_usr(Msg[i]);
		end;
*)
	end; { of case }
END;

PROCEDURE FixSendMenu(theMenu:menuHandle);
BEGIN
	if pucb = NIL then
		begin
		DisableItem(theMenu,0);
		CheckItem(theMenu,1,false);
		exit(FixSendMenu);
		end;

	CheckItem(theMenu,1,pucb^.u_echom=LOCAL);
	if pucb^.u_echongo = NORMALMODE then
		EnableItem(theMenu,1)
	else
		DisableItem(theMenu,1);
	if (pucb^.u_tcconn^.conn_state = ESTAB) then
		EnableItem(theMenu,0)
	else
		DisableItem(theMenu,0);
	if (pucb^.u_sendm = EVERYC) then
		DisableItem(theMenu,5)
	else
		EnableItem(theMenu,5);
END;

{ Print the foreign host message }

PROCEDURE pr_tn;
begin
	if (pucb = NIL) or (pucb^.u_tcconn = NIL) then
		Error(StrCvt('TELNET: No open connections'))
	else
		begin
		EmStr('To host ');
		EmStr(pucb^.u_tnshost);
		EmStr(' (');
		cvt_inaddr(pucb^.u_tcconn^.ForeignHost,Msg);
		EmStr(Msg);
		EmLn(')');
		end;
end;

{ Print telnet statistics }

PROCEDURE showstats;
begin
	if pucb = NIL then exit(showstats);
	pr_tn;
	case (pucb^.u_echom) of
		LOCAL:	   msg := 'local';
		REMOTE:	   msg := 'remote';
		otherwise  msg := 'invalid state';
		end;

	WriteLn('');
	WriteLn('Echo Mode: ',msg);

	case (pucb^.u_sendm) of
		EVERYC:	   msg := 'every character';
		NEWLINE:   msg := 'newline';
		otherwise  msg := 'invalid state';
		end;

	WriteLn('Send Mode: ',msg);
	tc_status(pucb^.u_tcconn);
end;

PROCEDURE tcpfull;
begin
	pucb^.u_tcpfull := true;
	Message(StrCvt('TELNET'),StrCvt('Output buffer full'));
end;

{ figure out a neat telnet socket }

FUNCTION tn_sock:unsigned;
VAR     temp:LongInt;
begin
	temp := TickCount;
	temp := BitAnd(temp,$0000ffff);
	if(temp < 1000) then temp := temp + 1000;
	tn_sock := temp;
end;

{ This has to be a separate task because resolve_name blocks }

PROCEDURE ResolveTask(Dummy:PTR);
VAR fhost:in_name;
BEGIN
	fhost := resolve_name(TheName);
	if (fhost = 0) then begin
		Error3(StrCvt('Foreign host '),@TheName,StrCvt(' not known.'));
		tn_free;
		nm_task := NIL;
		tk_exit;
		end;

	if (fhost = 1) then begin
		Error(StrCvt('Name servers not responding.'));
		tn_free;
		nm_task := NIL;
		tk_exit;
		end;

	pucb^.u_tnshost := copy(TheName,1,length(TheName));

	pucb^.u_tcconn :=
		tcp_open(@fhost,TheFSock,tn_sock,TCPWINDOW,TCPLOWIND,
		@opn_usr,@wr_usr,@mst_run,@cls_usr,@tmo_usr,@pr_dot,@bfr);
	pr_tn;
	EmStr('Trying...');
	nm_task := NIL;
	tk_exit;
END;

FUNCTION tn_conn(var s:STR255; fsock:Integer):ucbptr;
BEGIN
	if nm_task <> NIL then
		begin
		Error(StrCvt('Already trying to connect.'));
		tn_conn := NIL;
		exit(tn_conn);
		end;

	TheName := s;
	TheFSock := fsock;
	nm_task := tk_fork(tk_cur,@ResolveTask,TNTKSZ,'ResNam',NIL);
	if nm_task = NIL then
		begin
		CantAlloc(StrCvt('TELNET'),StrCvt('name resolution task'));
		tn_conn := NIL;
		exit(tn_conn);
		end;
	tn_conn := tn_alloc;
end;

{ Here begins commented-out code saved for possible later use }

(*      union { in_name _l; char _c[4]; } him;
	static char nmbuffer[20];

	him._l = custom.c_fhost;
	if(him._l == 0L) then begin
		Error('No telnet state saved, can''t continue.');
		netclose;
		halt;
		end;

	sprintf(nmbuffer, "%o,%o,%o,%o", him._c[0]&0xff,
		him._c[1]&0xff, him._c[2]&0xff, him._c[3]&0xff);
	s := nmbuffer;
	tnhost := custom.c_fhost;
	pr_banner(s);
	EmStr('Trying...');
	tcp_restore;

	2:
		begin
		if(NOT tcp_save) then
			begin
			Error('Suspend failed!');
			exit(TelnetMenuCmd);
			end;
		tn_free;
		end;

	13:
		begin
		me._l := in_mymach(tnhost);
		sprintf(buffer, '%u.%u.%u.%u ',
			me._c[0]&0xff, me._c[1]&0xff,
			me._c[2]&0xff, me._c[3]&0xff);

		ich := 0;
		while buffer[ich] <> chr(0) do
		begin
			if(tc_put(pucb^.u_tcconn,buffer[ich])) then
			begin
			tcpfull;
			goto 418;
			end;
			if(pucb^.u_echom = LOCAL) then
			Write(buffer[ich]);
			ich := ich + 1;
			end;
		418:

		       if(pucb^.u_sendm = EVERYC) then tcp_ex(pucb^.u_tcconn);

		       pr25(0,'Send my internet address in decimal.');
		       end;

	14:
		begin
		me._l := in_mymach(tnhost);
		sprintf(buffer, '%o,%o,%o,%o ',
			me._c[0]&0xff, me._c[1]&0xff,
			me._c[2]&0xff, me._c[3]&0xff);

		ich := 0;
		while buffer[ich]<>chr(0) do
		begin
			if(tc_put(pucb^.u_tcconn,buffer[ich])) then
			begin
			tcpfull;
			goto 93;
			end;
			if(pucb^.u_echom = LOCAL) then
			v(buffer[ich]);
			ich := ich + 1;
		end;
		 93:
		if(pucb^.u_sendm = EVERYC) then tcp_ex(pucb^.u_tcconn);
		pr25(0,'Send Internet address in octal');
		end;
*)

end.

!E!O!F!
#
#
echo extracting net/udp_lib.text...
cat >net/udp_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
{$DECL ALLOCT}
{$SETC ALLOCT := false}
UNIT UDP_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

INTERFACE

{$L-}

   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-PackIntf	  } PackIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-err_lib	  } Err_Lib,
      {$U net-ip_lib	  } IP_Lib,
      {$U net-icmp_lib	  } ICMP_Lib,
      {$U net-calls	  } Call_Lib;

{$L+}

TYPE

{ UDP Header structure }

    udp = PACKED RECORD
	     ud_srcp: integer;	        { source port }
	     ud_dstp: integer;	        { dest port }
	     ud_len: integer;	        { length of UDP packet }
	     ud_cksum: integer;	        { UDP checksum }
	    END;

    Ref_udp = ^ udp;


{ The UDP Connection structure }

    REF_udp_conn = ^ udp_conn;

    udp_conn = PACKED RECORD
		    u_next: REF_udp_conn;
		    u_lport: integer;	    { local port }
		    u_fport: integer;	    { foreign port }
		    u_fhost: in_name;	    { foreign host }
		    {int (*u_rcv)(); }	    { incoming packet handler }
		    u_rcv: ProcPTR;	    { incoming packet handler }
		    u_data: PTR;	    { fooish thing }
	    END;

    UDPCONN = REF_udp_conn;

PROCEDURE UdpInit;

FUNCTION udp_head(pip: Ref_ip):Ref_Udp;
FUNCTION udp_data(pup: Ref_Udp): PTR;

PROCEDURE udp_free(pkt:Packet);
FUNCTION udp_alloc(datalen: Integer; optlen: Integer): PACKET;

FUNCTION udp_socket: Integer;
FUNCTION udp_open(fhost: in_name; fsock: Integer; lsock: Integer;
		  handler: ProcPtr; data: PTR): UDPCONN;
PROCEDURE udp_close(con: UDPCONN);
FUNCTION udp_write(u: UDPCONN; p: PACKET; len: Integer): Integer;

FUNCTION udp_ckcon(fhost: in_name; fsock:integer): UDPCONN;

PROCEDURE udp_table;

IMPLEMENTATION

{ Some UDP internals }

CONST
	NAMESOCK       = 42;	        { A Well Known Socket }

	NI_NAME =       1;
	NI_ADDR =       2;
	NI_ERR  =       3;

	INPKTSIZ        =       INETLEN;

TYPE
    ph = PACKED RECORD
	    ph_src: in_name;	        { source address }
	    ph_dest: in_name;	        { dest address }
	    ph_zero: byte;	        { zero (reserved) }
	    ph_prot: byte;	        { protocol }
	    ph_len: integer;	        { udp length }
	    END;

{ Some goodly constants, macros and an external }
CONST

	UDPPROT =       17		    { UDP Internet protocol number };
	UDPLEN  =       sizeof(udp);

{$S	   }

VAR
	socket : integer;	        { Initialized in UDPInit }
	firstudp: UDPCONN;	        { Initialized in UdpInit := 0; }
	udp_ip_connection: IPCONN;      { IP connection used by UDP }

{$S UDPShare}

PROCEDURE udpdemux(p: PACKET; len: Integer; host: in_name); forward;

FUNCTION udp_head{(pip: Ref_ip):Ref_Udp};
BEGIN
   udp_head := { (struct udp *) } POINTER(ORD4(in_data(pip)));
END;

FUNCTION udp_data{(pup:Ref_Udp): PTR};
BEGIN
   udp_data :=  { (char *) } POINTER(ORD4(pup) + sizeof(udp) );
END;

{$S InitSeg }

{ Initialize the UDP layer; get an internet connection, initialize the
	demux table }

PROCEDURE UdpInit;
BEGIN
	FirstUDP := NIL;
	socket := 0;

	udp_ip_connection := in_open(UDPPROT, @udpdemux);
	if (udp_ip_connection = NIL) THEN BEGIN
		CantConnect(StrCvt('UDP'),StrCvt('InterNet'));
		END
{$IFC DEBUG}
	else if BCBitAnd(NDEBUG,INFOMSG) THEN
		WriteLn('UDP: Opened InterNet connection.');
{$ENDC}
END; { UdpInit }

{$S UDPShare}

FUNCTION udp_alloc(datalen: Integer; optlen: Integer): PACKET;
VAR
	len: Integer;
BEGIN
       {  len := (datalen + sizeof(udp) + 1) & ~1; }
       len := datalen + sizeof(udp) + 1;
       IF Odd(len) THEN len := len - 1;

       udp_alloc := in_alloc(len, optlen);
END; { udp_alloc }

PROCEDURE udp_free(pkt:Packet);
{$IFC ALLOCT}
VAR TmpStr:STR255;
{$ENDC}
BEGIN
{$IFC ALLOCT}
  Write('free: ');
  NumToString(ORD4(pkt),TmpStr);
  WriteLn(TmpStr);
{$ENDC}
  in_free(pkt);
END;

{ Create a UDP connection and enter it in the demux table. }

FUNCTION udp_open(fhost: in_name; fsock: Integer; lsock: Integer;
		  handler: ProcPtr; data: PTR): UDPCONN;
{	  fhost: in_name;    }	        { foreign host }
{	  fsock: integer;    }	        { foreign socket }
{	 lsock: integer;     }	        { local socket }
{       int (*handler)();    }	        { upcalled on receipt of a packet }
{	  data: PTR;	 }	        { random data }
VAR
	i: Integer;
	con: UDPCONN;
	ocon: UDPCONN;
BEGIN
{$IFC DEBUG}
	if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN
		Write('UDP_OPEN: On host ');
		out_inaddr(fhost);
		Write(', local sock ',lsock,', forn sock ',fsock,', foo ');
		WriteLong(ORD4(data));
		WriteLn('');
		END;
{$ENDC}

	con := firstudp;
	WHILE (con <> NIL ) DO BEGIN
		if (con^.u_lport = lsock) AND (con^.u_fport = fsock) AND
					    (con^.u_fhost = fhost) THEN BEGIN
{$IFC DEBUG}
			if BCBitAnd(NDEBUG,INFOMSG) OR
			   BCBitAnd(NDEBUG,PROTERR) THEN
				WriteLn('UDP: Connection already exists.');
{$ENDC}
			udp_open := NIL;
			EXIT(udp_open);
			END;

		ocon := con;
		con := con^.u_next;
		END;

	con := {(UDPCONN*) } POINTER(ORD4(NewPtr(sizeof(udp_conn))));
	if (con = NIL) THEN BEGIN
		CantAlloc(StrCvt('UDP'),StrCvt('connection'));
		udp_open := NIL;
		EXIT(udp_open);
		END;

	if (firstudp <> NIL) THEN ocon^.u_next := con
	    ELSE firstudp := con;

	con^.u_next := NIL;

	con^.u_lport := lsock;	        { fill in connection info }
	con^.u_fport := fsock;
	con^.u_fhost := fhost;
	con^.u_rcv   := handler;
	con^.u_data  := data;

	udp_open := con;
END; { udp_open }

FUNCTION udp_ckcon(fhost: in_name; fsock:integer): UDPCONN;
VAR
	con: UDPCONN;
BEGIN
	con := firstudp;
	while con <> NIL DO BEGIN
		IF (con^.u_fport = fsock) AND (con^.u_fhost = fhost)
		    THEN leave
		    ELSE con := con^.u_next;
		END;
	udp_ckcon := con;
END;  {	 end of udp_ckcon()  }

{ close a udp connection - remove the connection from udp's list of
	connections and deallocate it.
   But only if the connection is not null.  1/16/84 <J. H. Saltzer>
}

PROCEDURE udp_close(con: UDPCONN);
VAR
	pcon: UDPCONN;
BEGIN

	if (con = NIL) THEN EXIT(udp_close);

	{ This next line of code makes no sense: if the firstudp happens
	  to be deallocated (closed), then the entire chain is thrown away,
	  since there is no way to pick up the chain -- I think that
	  firstudp should be made to point at the next udp connection
	  in the list, just as if other udp connections were closed, then
	  they would be linked over (to preserve other connections). }

	if (firstudp = con) THEN BEGIN
		{ This is rewritten to meet my expectations }
		{ was: firstudp := NIL }
		firstudp := firstudp^.u_next;
		END
	else BEGIN
		pcon := firstudp;
		WHILE (pcon <> NIL) DO BEGIN
			IF pcon^.u_next = con THEN leave;
			pcon := pcon^.u_next;
			END;

		if (pcon = NIL) THEN BEGIN
{$IFC DEBUG}
		    WriteLn('UDPClose: could not find connection to close');
{$ENDC}
		    EXIT(udp_close);
		    END;

		pcon^.u_next := con^.u_next;
		END;

	DisposPtr(POINTER(ORD4(con)));
END; { udp_close}

FUNCTION udp_socket{: Integer};
BEGIN
    if (socket<> 0) THEN BEGIN
	    udp_socket := Socket;
	    Socket := Socket + 1;
	    EXIT(udp_socket);
	    END;

    socket := LoWord(TickCount);
    if (socket < 1000) THEN socket := socket + 1000;
    udp_socket := socket;
    socket := socket + 1;
END; { udp_socket }

{ This routine handles incoming UDP packets. They're handed to it by the
	internet layer. It demultiplexes the incoming packet based on the
	local port and upcalls the appropriate routine. }

PROCEDURE udpdemux(p: PACKET; len: Integer; host: in_name);
VAR
	pip: Ref_IP;
	pup: Ref_udp;
	php: ph;
	con: UDPCONN;
	osum, xsum: integer;
	data: PTR;
	plen: integer;
	RawDataPtr: PTR;
	Dummy: Integer;	        { Return value from ICMP call }
BEGIN
	CheckTask;

	{ First let's verify that it's a valid UDP packet. }
{$IFC DEBUG}
	if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN
		Write('UDP: Received packet of length ',len,
			' from host ');out_inaddr(host);
		WriteLn('.');
		END;
{$ENDC}

	pip := in_head(p);
	pup := udp_head(pip);
	plen := { bswap } (pup^.ud_len);

	if(plen > len) THEN BEGIN
{$IFC DEBUG}
		if BCBitAnd(NDEBUG,INFOMSG) OR BCBitAnd(NDEBUG,PROTERR) THEN BEGIN
			WriteLn('UDP: Received bad len: rcvd: ',len,
				', hdr: ',{ bswap } (pup^.ud_len) + UDPLEN);
			END;
{$ENDC}

		in_free(p);
		exit(udpdemux);
		END;

	osum := pup^.ud_cksum;
	if (osum <> 0) THEN BEGIN
		if Odd(len) THEN BEGIN
			RawDataPtr := POINTER( ORD4(pup) + len);
			RawDataPtr^ := 0;
			END;
		php.ph_src := host;
		php.ph_dest := pip^.ip_dest;
		php.ph_zero := 0;
		php.ph_prot := UDPPROT;
		php.ph_len  := pup^.ud_len;

		pup^.ud_cksum := cksum(@php, sizeof(ph) div 2);
		xsum := BitNOT(cksum(POINTER(ORD4(pup)),(len+1) div 2));
		pup^.ud_cksum := osum;
		if (xsum <> osum) THEN BEGIN
{$IFC DEBUG}
			if BCBitAnd(NDEBUG,INFOMSG) OR BCBitAnd(NDEBUG,PROTERR)
			   THEN WriteLn('UDP: Received bad checksum.');
{$ENDC}
			in_free(p);
			exit(udpdemux);
			END;
		END;

	{ udpswap(pup); } { Swapping unnecessary since 68000 doesnot byte swap
			     integer representations }
{$IFC DEBUG}
	if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN
		Write('UDP: Got packet for port ',pup^.ud_dstp,
		      ' from port ',pup^.ud_srcp,' on host ');
		out_inaddr(host);
		WriteLn('.');
		END;
{$ENDC}

	{ ok, accept it. run through the demux table and try to upcall it }

	con := firstudp;
	WHILE (con <> NIL) DO
	BEGIN
		if (con^.u_lport <> 0) AND (con^.u_lport <> pup^.ud_dstp)
			THEN con := con^.u_next
		ELSE BEGIN
{$IFC DEBUG}
		    if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN
			Write('UDPDEMUX: Foo Data := ');
			WriteLong(ORD4(con^.u_data)); WriteLn('');
			END;
{$ENDC}

		    if (con^.u_rcv <> NIL) THEN
			    CALL4A(POINTER(ORD4(p)),plen-UDPLEN,host,
				   con^.u_data,con^.u_rcv);
		    exit(udpdemux);
		    END;
		END;

{$IFC DEBUG}
	if BCBitAnd(NDEBUG,INFOMSG) OR BCBitAnd(NDEBUG,PROTERR) THEN
		WriteLn('UDP: No connection for packet; packet dropped.');
{$ENDC}

	{ send destination unreachable }
	Dummy := icmp_destun(host, in_head(p), DSTPORT);

	udp_free(p);
END; { udpdemux }

{ Fill in the udp header on a packet, checksum it and pass it to
	Internet. }

FUNCTION udp_write(u: UDPCONN; p: PACKET; len: Integer): Integer;
VAR
	pup: Ref_UDP;
	php: ph;
	udplen: Integer;
	host: in_name;
	RawDataPtr: PTR;
BEGIN

{$IFC DEBUG}
	if BCBitAnd(NDEBUG,TCTRACE) THEN BEGIN
	    WriteLn('UDP: Sending packet, length ',len,
		    ' lport ',u^.u_lport,', fport ',u^.u_fport,'.');
	    END;
	if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN
	    Write('UDP: buffer address ');
	    WriteLong(ORD4(p^.nb_buff));
	    WriteLn('');
	    END;
{$ENDC}

	pup := udp_head(in_head(p));
	udplen := len + sizeof(udp);
	if Odd(udplen)  THEN BEGIN
		{ (( byte*)pup)[udplen] := 0; }
		RawDataPtr := POINTER(ORD4(pup) + udplen);
		RawDataPtr^ := 0;
		END;

	host := u^.u_fhost;
	pup^.ud_len := udplen;
	pup^.ud_srcp := u^.u_lport;
	pup^.ud_dstp := u^.u_fport;
	{ udpswap(pup); } { No swapping is needed on the 68000 }

	php.ph_src := in_mymach(host);
	php.ph_dest := host;
	php.ph_zero := 0;
	php.ph_prot := UDPPROT;
	php.ph_len := pup^.ud_len;

	{ The next two statements look very strange: why is the checksum
	  being done twice on two different blocks with two different
	  lengths and put in the same place? I have a suspicion that the
	  first statement does a checksum over the header and stores that
	  checksum in the header, and then the entire packet with header
	  is checksumed and that value is then placed into the header as
	  well. This seems like a silly way to do checksumming }

	pup^.ud_cksum := cksum(@php, sizeof(ph) div 2);
	pup^.ud_cksum := BitNOT(cksum(POINTER(ORD4(pup)),(udplen+1) div 2));

	udp_write := in_write(udp_ip_connection, p, udplen, host);
END; {udp_ write}

(*
PROCEDURE udpswap(pup:Ref_udp);
{ This procedure is unnecessary on the 68000 but is left for historical reasons }
BEGIN
    pup^.ud_srcp :=  { bswap } (pup^.ud_srcp);
    pup^.ud_dstp := { bswap } (pup^.ud_dstp);
    pup^.ud_len := { bswap } (pup^.ud_len);
    pup^.ud_cksum := { bswap }(pup^.ud_cksum);
END; { udpswap }
*)

{ Dump the internal table of UDP connections. }

PROCEDURE TabTo(fromPos,toPos:Integer);
VAR i:Integer;
BEGIN
	for i := fromPos+1 to toPos do Write(' ');
END;

PROCEDURE udp_table;
CONST
	initItem = 6;
	udpDialog = 93;
VAR
	con: UDPCONN;
	tmp:STR255;
	it:INTEGER;
	dp:DialogPtr;
	iType:INTEGER;
	itemHndl:Handle;
	box:Rect;

    PROCEDURE SetIt(number:LONGINT);
    BEGIN
	NumToStr(number,tmp);
	GetDItem(dp,it,iType,itemHndl,box);
	SetIText(itemHndl,tmp);
    END;

BEGIN
	dp := GetNewDialog(udpDialog,NIL,POINTER(-1));
	con := firstudp;
	it := initItem;
	WHILE (con <> NIL) DO BEGIN
		SetIt(con^.u_lport);
		it := it + 1;
		SetIt(con^.u_fport);
		it := it + 1;
		cvt_inaddr(con^.u_fhost,tmp);
		GetDItem(dp,it,iType,itemHndl,box);
		SetIText(itemHndl,tmp);
		it := it + 1;
		SetIt(ORD4(con^.u_rcv));
		it := it + 1;
		SetIt(ORD4(con^.u_data));
		it := it + 1;
		con := con^.u_next;
		END;
	MsgRegister(dp);
END; { udp_table }

END.
!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting net/tcp_lib.text...
cat >net/tcp_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
{$DECL SAVEREST}
{$SETC SAVEREST := false}
{$DECL LISTEN}
{$SETC LISTEN := true}

{ Please note the copyright notice in the file "copyright/notice" }

UNIT TCP_Lib;

INTERFACE

{$L-}
   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-PackIntf	  } PackIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-err_lib	  } Err_Lib,
      {$U net-ip_lib	  } IP_Lib,
      {$U net-term_lib	  } Term_Lib,
      {$U net-calls	  } Call_Lib;
{$L+}

CONST
	TCPWINDOW       = 1000;	        { normal advertised window }
	TCPLOWIND       = 500;	        { low water mark on window }
	TCPRTK	        = 3072;
	TCPSTK	        = 3072;
	TCPTKSZ	        = TCPRTK + TCPSTK;
	BUFSIZE	        = 2048;
	TOTALCHUNKS     = 16;

TYPE
	unsigned = Integer;
	seq_t = LongInt;
	unshort = Integer;

	seq_no = RECORD CASE Integer OF
		    0: (a: seq_t);
		    1: (l: Integer;
			h: Integer);
		END; { p }

	Ref_tcp = ^tcp;
	tcp     = PACKED RECORD		        { a tcp header }
		tc_srcp: unshort;	        { source port }
		tc_dstp: unshort;	        { dest port }
		tc_seq: seq_t;		        { sequence number }
		tc_ack: seq_t;		        { acknowledgement number }
		tc_thl: 0..15;
		tc_res1: 0..15;
		tc_res2: 0..3;
		tc_furg: BOOLEAN;
		tc_fack: BOOLEAN;
		tc_psh: BOOLEAN;
		tc_rst: BOOLEAN;
		tc_syn: BOOLEAN;
		tc_fin: BOOLEAN;
		tc_win: unshort;	        { window }
		tc_cksum: unshort;	        { checksum }
		tc_urg: unshort;	        { urgent pointer }
		END;

{ TCP pseudo-header structure, used for checksumming }
	tcpph = PACKED RECORD	        { psuedo-header }
		tp_src: in_name;        { source addr }
		tp_dst: in_name;        { dest addr }
		tp_zero: BYTE;	        { always 0 }
		tp_pro: BYTE;	        { protocol }
		tp_len: Integer;        { length }
	END;
	Ref_tcpph = ^ tcpph;

	Ref_tcconn = ^tcp_conn;

    tcp_conn = PACKED record	    { record representing a TCP connection }
	tc_next:        Ref_tcconn; { queue link }
	tc_type:        QTypes;	    { type word for Mac queues }
	conn_state:     Integer;    { connection state }
	ForeignHost:    in_name;    { what foreign host it's connected to }
	SourcePort:     Integer;    { id of local port }
	DestPort:       Integer;    { id of remote port }
	TelLowWin:      Integer;
	TelWin:	        Integer;
	tcptm:	        Ref_Timer;  { The resend timer }
	tmack:	        Ref_Timer;  { ack timer }
	opbi:	        PACKET;	    { ptr to output packet buffer }
	otp:	        Ref_tcp;    { ptr to output pkt tcp hdr }
	odp:	        PTR;	    { ptr to start of output pkt data }
	ophp:	        Ref_tcpph;  { tcp pseudo hdr for cksum calc }
	frn_win:        unsigned;   { Size of foreign window. }
	ack_time:       LongInt;    { When last ACK was sent.  }
	odlen:	        Integer;    { bytes of data in output pkt }
	dally_time:     Integer;    { time to delay ack (ticks) }
	retry_time:     Integer;    { retransmission timeout (ticks)}
	resend:	        Integer;    { flag indicating data must be resent }

	taken:	        Integer;    { number of bytes of circbuf processed }
	avail:	        Integer;    { number of circbuf bytes unprocessed }
	fin_offset:     Integer;    { offset to finish of data }
	circbuf:        packed array [0..BUFSIZE-1] of char;  { data buffer }
	numchunks: Integer;
	maxchunks: Integer;
	lastchunk: Integer;
	ceilchunk: Integer;
	first: packed array [0..TOTALCHUNKS-1] of Integer;
	last: packed array [0..TOTALCHUNKS-1] of Integer;
	free: packed array [0..TOTALCHUNKS-1] of Boolean;

{int    (*tc_ofcn)();}	        { user function called on open }
{int    (*tc_dispose)();}       { user function to receive data }
{int    (*tc_yield)();}	        { Predicate set when user must run. }
{int    (*tc_cfcn)();}	        { user function called on close }
{int    (*tc_tfcn)();}	        { user function called on icp tmo }
{int    (*tc_rfcn)();}	        { user function called on icp resend }
{int    (*tc_buff)();}	        { user function to upcall when output buffer
					space is available }

	tc_ofcn, tc_dispose, tc_yield, tc_cfcn, tc_tfcn,
	tc_rfcn, tc_buff: ProcPtr;

	blk_inpt:       Boolean;    { prevents new data after close req }
	fin_rcvd:       Boolean;    { received end of file (FIN) }
	newsend:        Boolean;    { flag indicating new data to send }
	hasrecvd:       Boolean;    { does connection have data to process ? }
	mustsend:       Boolean;    { does connection need a send done? }
	end;

PROCEDURE tcp_init(stksiz: Integer);

FUNCTION tcp_open(fh: Ref_in_name; fs: unsigned; ls: unsigned;
		  win:Integer; lowwin: Integer;
		  ofcn,infcn,yldfcn,cfcn,tmofcn,rsdfcn,buff:ProcPtr)
	 :Ref_tcconn;

{$IFC LISTEN}
FUNCTION tcp_listen(ls: unsigned; win:Integer; lowwin: Integer;
		     ofcn,infcn,yldfcn,cfcn,tmofcn,rsdfcn,buff:ProcPtr)
	 :Ref_tcconn;
{$ENDC}

PROCEDURE tcp_close(connection:Ref_tcconn);

FUNCTION tc_fput(connection:Ref_tcconn; c: char): Boolean;
FUNCTION tc_put(connection:Ref_tcconn; c: char): Boolean;

PROCEDURE tcpurgent(connection:Ref_tcconn);
PROCEDURE tcp_ex(connection:Ref_tcconn);

PROCEDURE tc_status(connection:Ref_tcconn);

{ tcp connection states }
CONST
	CLOSED	        = 0;	        { nothing has happenned		    }
			{ CLOSED,SYNSENT,ESTAB,TIMEWAIT same as in tcp spec }
	SYNSENT	        = 1;	        { connection requested		    }
	SYNRCVD	        = 2;
	ESTAB	        = 3;	        { connection established	    }
	FINSENT	        = 4;	        { local user wishes to close	    }
					{ same as FIN-WAIT-1 in tcp spec    }
	FINRCVD	        = 5;	        { foreign host wishes to close	    }
					{ same as CLOSE-WAIT in tcp spec    }
	SIMUL	        = 6;    { local user and foreign host wish to close }
					{ same as CLOSING in tcp spec	    }
	FINACKED        = 7;	        { local user's close request acked  }
					{ same as FIN-WAIT-2 in tcp spec    }
	R_AND_S	        = 8;    { frgn close req rcvd, local close req sent }
					{ same as LAST-ACK in tcp spec	    }
	TIMEWAIT        = 9;	        { last ack is being sent	    }
	LISTEN	        = 10;	        { waiting for connection request    }
					{ new for Macintosh version	    }

{ Picture from TCP Specs (RFC 793):

  TCP Header Format

    0		        1		    2		        3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |	      Source Port	   |	   Destination Port	   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |			    Sequence Number			   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |		        Acknowledgment Number			   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Data |	       |U|A|P|R|S|F|				   |
   | Offset| Reserved  |R|C|S|S|Y|I|	        Window		   |
   |	   |	       |G|K|H|T|N|N|				   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |	       Checksum		   |	     Urgent Pointer	   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |		        Options			   |    Padding	   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |				 data				   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
}

IMPLEMENTATION

{$IFC DEBUG}
   PROCEDURE WriteHex(c:char);	  { writes a char as two hex digits }
   VAR	   temp:Integer;
   BEGIN
	temp := BitAnd(BitSR(ord(c),4),$000f);
	if (temp <= 9) then temp := temp + ord('0')
	else temp := temp + ord('A') - 10;
	Write(chr(temp));
	temp := BitAnd(ord(c),$000f);
	if (temp <= 9) then temp := temp + ord('0')
	else temp := temp + ord('A') - 10;
	Write(chr(temp));
   END;

PROCEDURE Print_TCP(t:Ref_TCP);
BEGIN
	Write('TCP Header: ');
	WriteLn('Source Port = ',t^.tc_srcp:1,', Dest Port = ',t^.tc_dstp:1);
	WriteLn('Sequence number = ',t^.tc_seq:1,', Ack number = ',t^.tc_ack:1);
	{ Write('Bits = ');
	WriteHex(chr(BitAND($00ff,BitSR(t^.tc_bits,8))));
	WriteHex(chr(BitAND($00ff,t^.tc_bits))); }
	Write('Window = ',t^.tc_win:1);
	WriteLn(', Checksum = ',t^.tc_cksum:1,', Urgent Pointer = ',t^.tc_urg:1);
END;
{$ENDC}

CONST
	TCPPROT	        = 6;	        { tcp protocol number }
	TCPMAXWIND      = 4096;	        { maximum tcp window that this
					   implementation supports }

{ Translation of Dave Clark's Alto TCP into C.

  This file contains a bare-bones minimal TCP, suitable only for user Telnet
  and other protocols which do not need to transmit much data.  Its primary
  weakness is that it does not pay any attention to the window advertised
  by the receiver; it assumes that it will never be sending much data and
  hence will never run out of receive window.  (Note: this not really that
  much of a weakness, since the other side should ignore any data over its
  window.  Still, it would be more efficient.)  Its other major weakness
  is that it will not handle out-of-sequence packets; any out-of-sequence
  data received is ignored.

  This TCP requires the tasking package to run.	 It runs as two tasks:
  TCPsend, which handles all data transmission, and TCPprocess, which
  processes the incoming data from the circular buffer. The data is placed
  in the circular buffer by tcp_rcv, which is upcalled from internet on an
  arriving packet.
}

CONST
	ACKDALLY        = 30;	        { wait between ACKs (ticks) }
	INITRT	        = 4;	        { retry time -- initial request }
{$IFC DEBUG}
	CONNRT	        = 20;
{$ELSEC}
	CONNRT	        = 2;	        { retry time -- after connected }
{$ENDC}
	MAXBUF	        = 500;	        { maximum output buffer size }
	ICPTMO	        = 5;	        { initial conn. timeout }

{ Connection queue structure: compatible with Mac queues }
TYPE    TCconnQ = PACKED RECORD
		qFlags:INTEGER;
		qHead:Ref_tcconn;
		qTail:Ref_tcconn
		end;

     TCPGlobals = RECORD
	tcpconns:^TCconnQ;	 { Global connection queue }
	still_data: Boolean;    { There are still incoming pkts to process. }
	tcpfd: IPCONN;	        { connection ID used when calling Internet }
	TCPsend: Ref_Task;      { tcp sending task }
	TCPprocess: Ref_Task;   { TCP data processing task }
	first_offset: Integer;

	{ TCP statistics collection variables }

	tcppsnt:unsigned;       { number of packets sent }
	tcpprcv:unsigned;       { number of packets received }
	tcpbsnt:unsigned;       { number of bytes sent }
	tcpbrcv:unsigned;       { number of bytes received }
	tcprack:unsigned;       { # of bytes received and acked }
	tcpsock:unsigned;       { # of packets not for my socket }
	tcpresend:unsigned;     { # of resent packets }
	tcprercv:unsigned;      { # of old packets received }
	tcpous:unsigned;        { # of out-of-sequence packets }
	bd_chk:unsigned;        { Number of pkts rcvd with bad checksums.}
	ign_win:unsigned;       { Num pkts rcvd that ignored our window. }
	END;

	Ref_TCPGlobals = ^TCPglobals;

VAR global:Ref_TCPGlobals;

PROCEDURE tcp_rcv(inpkt: PACKET; len: Integer; fhost: in_name); FORWARD;
PROCEDURE tcp_send(NoArg:PTR); FORWARD;
PROCEDURE tcp_process(NoArg:PTR); FORWARD;
PROCEDURE tmhnd(connection:Ref_tcconn); FORWARD;
PROCEDURE tcp_ack(connection:Ref_tcconn); FORWARD;
PROCEDURE send_wake(connection:Ref_tcconn); FORWARD;

{ translated macro }

FUNCTION  NOT_YET(a, b: Integer): Boolean;
BEGIN
   NOT_YET :=   ((a) < (b));
END; { NOT YET }

{ This routine is called to initialize the TCP.	 It starts up the tasking
  system, initiates the timer, TCPsend, and TCPrcv tasks.
  It does not attempt to open the connection; that
  function is performed by tcp_open. }

{$S InitSeg}

PROCEDURE tcp_init(stksiz: Integer);
BEGIN

	global := POINTER(ORD4(NewPtr(sizeof(TCPglobals))));
	if global = NIL then
		Fatal(StrCvt('TCP_INIT: can''t get globals'),false);

	WITH global^ DO BEGIN

	tcpfd := in_open(TCPPROT, @tcp_rcv);
	if (tcpfd = NIL) THEN
		Fatal(StrCvt('TCP_INIT: can''t open Internet'),false);

	TCPconns := POINTER(ORD4(NewPtr(sizeof(TCconnQ))));
	if TCPconns = NIL then
		Fatal(StrCvt('TCP_INIT: can''t allocate connection queue'),false);

	{ Initialize connection queue }
	TCPconns^.qFlags := 0;
	TCPconns^.qHead := NIL;
	TCPconns^.qTail := NIL;

	tcppsnt := 0;		        { initially, no pkts sent }
	tcpprcv := 0;		        { or received }
	tcpbsnt := 0;		        { no bytes sent }
	tcpbrcv := 0;		        { foreign host sent no bytes }
	tcprack := 0;		        { we acked no bytes of foreign host}
	tcpresend := 0;
	tcprercv := 0;
	ign_win := 0;
	still_data := FALSE;

	TCPsend := tk_fork(tk_cur, @tcp_send, TCPSTK, 'TCPsend', NIL);
	TCPprocess := tk_fork(TCPsend, @tcp_process, TCPRTK, 'TCPproc', NIL);

	{ let the other tasks get started }
	tk_yield;
	END; { WITH }
END; { end of tcp_init}

{$S	    }

{ tcp_alloc: allocate and return a tcp connection (for now just one allowed)
  the pointers to the up-callable user routines for open, received data,
  close and timeout.  }

FUNCTION tcp_alloc:Ref_tcconn;
VAR
    Dummy:Boolean;
    connection:Ref_tcconn;
    i:Integer;
    Fake:OSErr;
BEGIN
	{ Get connection from memory manager; put on queue of connections in use }

	connection := POINTER(ORD4(NewPtr(sizeof(tcp_conn))));
	if connection = NIL then
		begin
		CantAlloc(StrCvt('TCP'),StrCvt('connection'));
		tcp_alloc := NIL;
		exit(tcp_alloc);
		end;
	connection^.tc_next := NIL;
	connection^.tc_type := dummyType;
	enqueue(POINTER(ORD4(connection)),POINTER(ORD4(global^.TCPconns)));

	WITH connection^ do begin

	{ alloc and set up output pkt }
	opbi := in_alloc(INETLEN, 0);
	if (opbi = NIL) THEN BEGIN
		CantAlloc(StrCvt('TCP'),StrCvt('packet'));
		Fake := dequeue(POINTER(ORD4(connection)),
				POINTER(ORD4(global^.TCPconns)));
		DisposPtr(POINTER(ORD4(connection)));
		tcp_alloc := NIL;
		exit(tcp_alloc);
		END;

	otp := POINTER(ORD4(in_data(in_head(opbi))));
	odp := POINTER(ORD4(otp) + sizeof(tcp));

	{ allocate pseudoheader for outgoing packet }
	ophp := POINTER(ORD4(NewPtr(sizeof(tcpph))));
	if (ophp = NIL) THEN BEGIN
		Fake := dequeue(POINTER(ORD4(connection)),
				POINTER(ORD4(global^.TCPconns)));
		DisposPtr(POINTER(ORD4(connection)));
		tcp_alloc := NIL;
		exit(tcp_alloc);
		END;

	ophp^.tp_zero := 0;
	ophp^.tp_pro := TCPPROT;

	global^.tcpsock := 0;
	conn_state := CLOSED;	        { initially, conn. closed }
	retry_time := INITRT*TPS;
	newsend := FALSE;
	ForeignHost := 0;
	resend := 0;
	fin_offset := 0;
	fin_rcvd := FALSE;
	ack_time := 0;
	odlen := 0;
	blk_inpt := FALSE;
	hasrecvd := FALSE;
	mustsend := FALSE;

	otp^.tc_seq := 0;
	otp^.tc_ack := 0;
	otp^.tc_res1 := 0;
	otp^.tc_res2 := 0;
	otp^.tc_thl := 0;
	otp^.tc_furg := false;
	otp^.tc_fack := false;
	otp^.tc_psh := false;
	otp^.tc_rst := false;
	otp^.tc_fin := false;
	otp^.tc_syn := true;
	otp^.tc_urg := 0;

	global^.bd_chk := 0;
	taken := -1;
	avail := -1;

	tcptm := tm_alloc;
	if(tcptm = NIL) THEN BEGIN
		Fake := dequeue(POINTER(ORD4(connection)),
				POINTER(ORD4(global^.TCPconns)));
		DisposPtr(POINTER(ORD4(connection)));
		tcp_alloc := NIL;
		exit(tcp_alloc);
		END;

	tmack := tm_alloc;
	if(tmack = NIL) THEN BEGIN
		Dummy := tm_free(tcptm);
		Fake := dequeue(POINTER(ORD4(connection)),
				POINTER(ORD4(global^.TCPconns)));
		DisposPtr(POINTER(ORD4(connection)));
		tcp_alloc := NIL;
		exit(tcp_alloc);
		END;

	numchunks := 0;
	maxchunks := 0;
	lastchunk := 0;
	ceilchunk := 0;
	FOR i := 0 to TOTALCHUNKS - 1 DO free[i] := TRUE;

	end;{with}
	tcp_alloc := connection;
END;

PROCEDURE tcp_free(VAR conn:Ref_tcconn);
VAR     Dummy:OSErr;
	Fake:Boolean;
BEGIN
	{ dequeue the connection from the active list and the tcp_process queue,
	  then free its storage }
	Dummy := dequeue(POINTER(ORD4(conn)),POINTER(ORD4(global^.TCPconns)));
	in_free(conn^.opbi);
	Fake := tm_clear(conn^.tcptm);
	Fake := tm_free(conn^.tcptm);
	Fake := tm_clear(conn^.tmack);
	Fake := tm_free(conn^.tmack);
	DisposPtr(POINTER(ORD4(conn^.ophp)));
	DisposPtr(POINTER(ORD4(conn)));
	conn := NIL
END;

{ Initiate the TCP closing sequence.  This routine will return immediately;
  when the close is complete the user close function will be called. }

PROCEDURE tcp_close(connection:Ref_tcconn);
BEGIN
	if connection = NIL then exit(tcp_close);

	WITH connection^ do begin

	CASE conn_state OF
	LISTEN, SYNRCVD, CLOSED:
		BEGIN
		CALL(tc_cfcn);
		tcp_free(connection);
		END;
	ESTAB:
		BEGIN
		conn_state := FINSENT;
		otp^.tc_fin := true;
		otp^.tc_psh := false;
		blk_inpt := TRUE;
		newsend := TRUE;
		send_wake(connection);
		END;
	SYNSENT:
		BEGIN
		otp^.tc_rst := TRUE;
		blk_inpt := TRUE;
		newsend := TRUE;
		send_wake(connection);
		END;
	END;
	end;{with}
END; { end of tcp_close }

{ Close and reset a tcp connection. }

PROCEDURE tc_clrs(connection:Ref_tcconn; p: PACKET; fhost: in_name);
VAR     ltemp: LongInt;
	myport:unsigned;
	ptp: Ref_tcp;
	php: tcpph;
	err:Integer;
BEGIN
	if connection = NIL then exit(tc_clrs);

	WITH connection^,global^ do begin

	ptp := POINTER(ORD4(in_data(in_head(p))));

	{ swap port numbers }
	myport := ptp^.tc_dstp;
	ptp^.tc_dstp := ptp^.tc_srcp;
	ptp^.tc_srcp := myport;

	{ fill in the rest of the header }
	ltemp := ptp^.tc_seq;
	ptp^.tc_seq := ptp^.tc_ack;
	ptp^.tc_ack := ltemp;
	ptp^.tc_furg := false;
	ptp^.tc_fack := false;
	ptp^.tc_psh := false;
	ptp^.tc_fin := false;
	ptp^.tc_syn := false;
	ptp^.tc_thl := sizeof(tcp) div 4;
	ptp^.tc_rst := true;
	ptp^.tc_win := 0;
	{tcp_swab(ptp);} { Not on 68000 }

	{ Set up the tcp pseudo header }
	php.tp_src := in_mymach(fhost);
	php.tp_dst := fhost;
	php.tp_zero := 0;
	php.tp_pro := TCPPROT;
	php.tp_len := sizeof(tcp);

	{ checksum the packet }
	ptp^.tc_cksum := cksum(@php, sizeof(tcpph) div 2);
	ptp^.tc_cksum := BitNOT(cksum(POINTER(ORD4(ptp)),sizeof(tcp) div 2));

	err := in_write(tcpfd, p, sizeof(tcp), fhost);
	end;{with}
	tcp_free(connection);
END; {tc_clrs}

PROCEDURE cleanup(why: StringPtr);
BEGIN
	Error2(StrCvt('Closed:'),why);
END;

{ Just shift the data in a buffer, moving len bytes from from to to. }

PROCEDURE shift(from_ptr, to_ptr: PTR; len:Integer);
BEGIN
{$IFC DEBUG}
	if (len < 0) THEN BEGIN
		WriteLn('tcp: shift: bad arg--len < 0');
		EXIT(shift);
		END;
{$ENDC}

	WHILE (len<>0) DO BEGIN
	    len := len - 1;
	    to_ptr^ := from_ptr^;
	    to_ptr := POINTER(ORD4(to_ptr)+1);
	    from_ptr := POINTER(ORD4(from_ptr)+1);
	    END;
END; { shift }

{ This routine forms the body of the TCP data receiver task.
  It attempts to read and process incoming packets.
  The processing of each packet is divided into three phases:
       1) Processing acknowlegments.  This involves 'shifting' the data
	  in the output packet to account for acknowledged data.
       2) Processing state information: syn's, fin's, urgents, etc.
       3) Processing the received data.	 This is done by calling the
	  user's 'input data' function (specified in his call to
	  tcp_open), passing it the address and length of the received
	  data. }

PROCEDURE tcp_rcv(inpkt: PACKET; len: Integer; fhost: in_name);
VAR
	itp:	        Ref_tcp;    { input pkt tcp hdr }
	idp:	        PTR;	    { input pkt data ptr }
	needed_acking:  Integer;    { number of outstanding bytes }
	acked:	        Integer;    { # outstanding bytes acked by this packet }
	data_acked:     Integer;    { # outstanding bytes acked by this packet }
				    { number of previously received seq. numbers}
	prev_rcvd:      Integer;    { (data + fin bit) in current packet}
	idlen:	        Integer;    { len of input pkt in bytes }
	i:	        Integer;
	limit:	        Integer;
	Raw_Ptr:        PTR;
	tempsum:        unsigned;   { temp variable for a checksum }
	Dummy:	        Boolean;
	connection:     Ref_tcconn;
	ip_hd:	        Ref_IP;	    { used to extract fhost for checksum }
	start_offset:   Integer;
	end_offset:     Integer;
	notfound:       Boolean;
	iphp:	        tcpph;	    { pseudo-header for checksum calcs }
	Fake:	        OSErr;
BEGIN
	CheckTask;

	WITH global^ DO BEGIN
	{ Process the received input packet }

	itp := POINTER(ORD4(in_data(in_head(inpkt))));
	idp := POINTER(ORD4(itp) + (itp^.tc_thl*4));

	{ get incoming data length }
	idlen := len - (itp^.tc_thl * 4);
	tcpprcv := tcpprcv + 1;	        { another packet received }

{$IFC DEBUG}
	WriteLn('TCP: Received packet, len = ',len:1,', idlen = ',idlen:1);
	(* Print_TCP(itp); *)
{$ENDC}

	{ compute incoming tcp checksum here... }
	if idlen >= 0 THEN
		BEGIN
		Raw_Ptr := POINTER(ORD4(idp) + idlen);
		Raw_Ptr^ := 0;
		END;

	iphp.tp_zero := 0;
	iphp.tp_pro := TCPPROT;
	iphp.tp_len := idlen + (itp^.tc_thl*4);
	ip_hd := in_head(inpkt);
	iphp.tp_dst := in_mymach(ip_hd^.ip_src);
	iphp.tp_src := ip_hd^.ip_src;
	tempsum := itp^.tc_cksum;
	itp^.tc_cksum := cksum(@iphp,sizeof(tcpph) div 2);
	itp^.tc_cksum := BitNOT(cksum(POINTER(ORD4(itp)),
				      ((idlen+1) div 2)+(itp^.tc_thl*2)));

	if (itp^.tc_cksum <> tempsum) THEN BEGIN
{$IFC DEBUG}
		WriteLn('TCP: Bad xsum was ',tempsum:1,
			'; should have been ',itp^.tc_cksum:1);
{$ENDC}
		bd_chk := bd_chk + 1;
		in_free(inpkt);
		EXIT(tcp_rcv);
		END;

	{ loop over each connection looking for match (demux) }

	connection := TCPconns^.qHead;
	notfound := true;
	while (connection <> NIL) and notfound do begin

		{ If the user wishes to send data, give up the processor. }
		if (CALLB(connection^.tc_yield)) THEN BEGIN
			still_data := TRUE;     { There's still data to process. }
			tk_yield;
			still_data := FALSE;
			END;

		if (itp^.tc_dstp = connection^.SourcePort) then
			begin
			if (connection^.conn_state = CLOSED) |
			   ((connection^.conn_state <> LISTEN)
			      & (itp^.tc_srcp <> connection^.DestPort))
				THEN BEGIN
				tcpsock := tcpsock + 1;
				in_free(inpkt);
				EXIT(tcp_rcv);
				END;
			notfound := false;
			end
		else    connection := connection^.tc_next;
		end; { of loop }

	if notfound then
		BEGIN
{$IFC DEBUG}
		WriteLn('Received TCP packet not for me');
{$ENDC}
		tcpsock := tcpsock + 1;
		in_free(inpkt);
		exit(tcp_rcv);
		END;

	WITH connection^ DO BEGIN

	if itp^.tc_rst THEN
		BEGIN
		IF (conn_state <> LISTEN) AND (conn_state <> CLOSED) THEN
			{ other guy's resetting me }
			BEGIN
			cleanup(StrCvt('foreign reset'));
			CALL(tc_cfcn);
			tcp_free(connection);
			END;
		in_free(inpkt);
		EXIT(tcp_rcv);
		END;

{ incoming packet processing is dependent on the state of the connection }

	if (conn_state = SYNSENT) THEN
		BEGIN
		{ opening the connection }

		{ ack must be for our initial sequence number - namely, 1 }
		if (NOT itp^.tc_fack) OR (itp^.tc_ack <> 1) THEN
			BEGIN
			in_free(inpkt);
			EXIT(tcp_rcv);
			END;

		if NOT itp^.tc_syn THEN
			BEGIN
			in_free(inpkt);
			EXIT(tcp_rcv);
			END;

		{ Connection open }
		otp^.tc_syn := false;
		otp^.tc_rst := false;
		otp^.tc_fack := true;
		otp^.tc_psh := true;
		otp^.tc_seq := 1;
		itp^.tc_seq := itp^.tc_seq + 1; { syn's take sequence no.space }
		otp^.tc_ack := itp^.tc_seq;
		tcpbrcv := 1;
		frn_win := itp^.tc_win;
		conn_state := ESTAB;
		retry_time := CONNRT*TPS;

		{ Our initial request was acknowledged and there is not yet
		  new data so reset the timer }
		newsend := TRUE;
		Dummy := tm_clear(tcptm);
		CALL(tc_ofcn);
		send_wake(connection);
		END
{$IFC LISTEN}
	else if conn_state = LISTEN then
		BEGIN
		{ New code (for Mac version) to accept incoming connections }

		if (NOT itp^.tc_syn) OR itp^.tc_fack THEN
			BEGIN
			tc_clrs(connection,inpkt,fhost);
			in_free(inpkt);
			EXIT(tcp_rcv);
			END;

		conn_state := SYNRCVD;
		ForeignHost := fhost;
		DestPort := itp^.tc_srcp;

		{ set up pseudoheader for outgoing packet }
		ophp^.tp_src := in_mymach(ForeignHost);
		ophp^.tp_dst := ForeignHost;

		{ set up outgoing packet }
		otp^.tc_srcp := SourcePort;
		otp^.tc_dstp := DestPort;
		otp^.tc_ack := itp^.tc_seq + 1;
		otp^.tc_fack := true;
		newsend := TRUE;
		send_wake(connection);
		in_free(inpkt);
		EXIT(tcp_rcv);
		END
	else if (conn_state = SYNRCVD) then
		BEGIN
		{ New (Mac) code to accept acknowledgement of opening }

		if itp^.tc_seq <> otp^.tc_ack then
			begin
{$IFC DEBUG}
			WriteLn('TCP_RCV: bad seq number (',itp^.tc_seq:1,
				') should be (',otp^.tc_seq:1,')');
{$ENDC}
			tc_clrs(connection,inpkt,fhost);
			in_free(inpkt);
			EXIT(tcp_rcv);
			end;

		if NOT otp^.tc_fack then
			begin
			in_free(inpkt);
			EXIT(tcp_rcv);
			end;
		if itp^.tc_ack <> (otp^.tc_seq + 1) then
			begin
{$IFC DEBUG}
			WriteLn('TCP_RCV: bad ack number (',itp^.tc_ack:1,
				'); should be ',otp^.tc_ack:1);
{$ENDC}
			tc_clrs(connection,inpkt,fhost);
			in_free(inpkt);
			EXIT(tcp_rcv);
			end;

		{ Connection open }
		otp^.tc_syn := false;
		otp^.tc_rst := false;
		otp^.tc_fack := true;
		otp^.tc_psh := true;
		otp^.tc_seq := itp^.tc_seq;
		otp^.tc_ack := itp^.tc_ack;
		tcpbrcv := 1;
		frn_win := itp^.tc_win;
		conn_state := ESTAB;
		retry_time := CONNRT*TPS;
		Dummy := tm_clear(tcptm);
		CALL(tc_ofcn);
		END
{$ENDC}
		;

	{ This code updates things based on incoming ack value }

	if itp^.tc_fack THEN
		BEGIN
		acked := ORD(itp^.tc_ack - otp^.tc_seq);
		needed_acking := odlen + ORD(otp^.tc_fin);
		IF acked = needed_acking
		    THEN data_acked := acked - ORD(otp^.tc_fin)
		    ELSE data_acked := acked;

{$IFC DEBUG}
		WriteLn('tcp_rcv: ACK processing (acked = ',acked:1,
			', needed_acking = ',needed_acking:1,')');
{$ENDC}

		{ Ack for unsent data prompts us to reset. }
		if acked > needed_acking THEN BEGIN
			otp^.tc_rst := true;
			Error(
			StrCvt('TCP_RCV: unsent data acknowledged (resetting)'));
			otp^.tc_seq := itp^.tc_ack;
			send_wake(connection);
			in_free(inpkt);
			EXIT(tcp_rcv);
			END;

		if acked > 0 THEN
			BEGIN
			{ So now we have some free output buffer space. Upcall
			  the client and tell him so. }
			if (odlen >= MAXBUF) THEN CALL(tc_buff);

			{ Account for ack of our urgent data by updating the
			  urgent pointer }
			if otp^.tc_furg THEN
				BEGIN
				otp^.tc_urg := otp^.tc_urg - acked;
				if (otp^.tc_urg <= 0) THEN
					BEGIN
					otp^.tc_urg := 0;
					otp^.tc_furg := false;
					END;
				END;

			{ See if all our outgoing data is now acked.
			  If so, turn off resend timer }
			if acked = needed_acking THEN
				BEGIN
				Dummy := tm_clear(tcptm);
				newsend := FALSE;
				resend := 0;
				odlen := 0;
				odp^ := 0;
				if otp^.tc_fin THEN
					BEGIN
					otp^.tc_fin := false;
					CASE (conn_state) OF
					FINSENT: conn_state := FINACKED;
					SIMUL: conn_state := TIMEWAIT;
					R_AND_S: BEGIN
						CALL(tc_cfcn);
						tcp_free(connection);
						in_free(inpkt);
						EXIT(tcp_rcv);
						END;
{$IFC DEBUG}
					OTHERWISE BEGIN
			      WriteLn('tcp_rcv: unexpected state: ',conn_state:1);
						END;
{$ENDC}
					END; { end of case }
					END; { end of fin test }
				END  { if acked = needed_acking }
			ELSE { acked is less than needed_acking }
				BEGIN
				{ Otherwise, shift the output data in the packet
				  to account for ack }
				odlen := odlen - data_acked;
				shift(POINTER(ORD4(odp)+data_acked), odp, odlen);
				Raw_Ptr := POINTER(ORD4(odp) + odlen);
				Raw_Ptr^ := 0;
				END;

			{ update the sequence number }
			frn_win := itp^.tc_win;
			otp^.tc_seq := otp^.tc_seq + acked;
		END;
	END;

	{ Now process the received data }

{$IFC DEBUG}
	WriteLn('tcp_rcv: About to process data');
{$ENDC}

	{ Check if the incoming data is over the top of our window }
	if ((itp^.tc_seq + idlen) > (otp^.tc_ack + otp^.tc_win)) THEN
		BEGIN
		{ ignore excess data }
		ign_win := ign_win + 1;
		idlen := idlen -
			 (itp^.tc_seq + idlen - (otp^.tc_ack + otp^.tc_win));
		END;

	{ Calculate the number of previously received sequence numbers
	  in the current packet }

	prev_rcvd := otp^.tc_ack - itp^.tc_seq;
	start_offset := 0 - prev_rcvd;
	end_offset := (start_offset + idlen) - 1;
	if end_offset >= BUFSIZE THEN end_offset := BUFSIZE - 1;

{$IFC DEBUG}
	WriteLn('tcp_rcv: prev_rcvd = ',prev_rcvd:1,', end_offset = ',end_offset:1,
		', idlen = ',idlen:1,', avail = ',avail:1);
{$ENDC}

	if itp^.tc_fin THEN
		BEGIN
		fin_rcvd := TRUE;
		fin_offset := end_offset;
		END;

	if end_offset < avail THEN    { If all incoming info is old.}
		BEGIN
{$IFC DEBUG}
		WriteLn('tcp_rcv: no new data in packet');
{$ENDC}
		tcprercv := tcprercv + 1;
		in_free(inpkt);
		EXIT(tcp_rcv);
		END;

	limit := prev_rcvd;
	if limit < 0 then limit := 0;
	FOR i := limit TO idlen - 1 DO
		BEGIN
		Raw_Ptr := POINTER(ORD4(idp)+i);
		circbuf[(taken+1+start_offset+i) mod BUFSIZE] := chr(Raw_Ptr^);
		END;

	if (numchunks = 0) THEN
		BEGIN
		if start_offset <= avail + 1 THEN     { DDC CHANGE }
			avail := end_offset
		else
			BEGIN
			first[0] := start_offset;
			last[0]	 := end_offset;
			free[0]	 := FALSE;
			numchunks := 1;
			maxchunks := 0;
			lastchunk := 0;
			END;
		END
	else
		BEGIN
		if (lastchunk > -1) & ((last[lastchunk] + 1) = start_offset) THEN
			last[lastchunk] := end_offset
		else BEGIN
			 FOR i := 0 TO maxchunks DO
			    BEGIN
			    if free[i] THEN cycle;
			    if (last[i]+1) < start_offset THEN cycle;
			    if first[i] > (end_offset+1) THEN cycle;
			    if end_offset < last[i] then end_offset := last[i];
			    if start_offset > first[i] then
				start_offset := first[i];
			    free[i] := TRUE;
			    numchunks := numchunks - 1;
			    if (numchunks = 0) THEN
				BEGIN
				maxchunks := -1;
				leave;
				END;
			    END;

			if (lastchunk > -1) & free[lastchunk] THEN lastchunk := -1;
			if (start_offset <= 0) THEN avail := end_offset
			else BEGIN
				limit := maxchunks + 1;
				if limit > TOTALCHUNKS-1 then
					limit := TOTALCHUNKS - 1;
				FOR i := 0 TO limit DO
					BEGIN
					if not free[i] THEN cycle;
					free [i] := FALSE;
					first[i] := start_offset;
					last [i] := end_offset;
					numchunks := numchunks + 1 ;
					if i = (maxchunks + 1) THEN maxchunks := i;
					if i > ceilchunk THEN ceilchunk := i;
					if lastchunk = -1 THEN lastchunk := i;
					leave;
					END;
				END;
			END;
		END;

{$IFC DEBUG}
	WriteLn('tcp_rcv: avail = ',avail:1,', fin_offset = ',fin_offset:1);
{$ENDC}

	{ If there's data to process, wake up the data processing task. }
	if (avail >= 0) OR (fin_offset = -1) THEN
		BEGIN
{$IFC DEBUG}
		WriteLn('tcp_rcv: awakening tcp_process');
{$ENDC}
		hasrecvd := true;
		tk_wake(TCPprocess);
		END;

	in_free(inpkt);
	end; { WITH connection^ }
	end; { WITH global^ }
END; {tcp_rcv}

{ Process received data from tcp. This is the task counterpart of tcp_rcv. }

PROCEDURE tcp_process(NoArg:PTR);
LABEL   666;
VAR     i:	        Integer;        { used for looping over data }
	Fake:	        Boolean;
	connection:     Ref_tcconn;
	ticks:	        LONGINT;        { used to regulate SystemTask calling }
BEGIN
	tk_block;       { Forget the initial wakeup }

666:    while true do begin
		connection := global^.tcpconns^.qHead;
		while connection <> NIL do
			if connection^.hasrecvd then leave
			else connection := connection^.tc_next;
		if connection = NIL then tk_block
		else leave;
		END;

{$IFC DEBUG}
	WriteLn('tcp_process: running....');
{$ENDC}

	WITH connection^,global^ do begin

{$IFC DEBUG}
	WriteLn('tcp_process: avail: ',avail:1,', fin_offset: ',fin_offset:1);
{$ENDC}

	if (avail >= 0) OR (fin_offset = -1) THEN BEGIN
		while in_more do
			begin
{$IFC DEBUG}
			WriteLn('tcp_process: yielding due to in_more');
{$ENDC}
			tk_yield;
			end;

		hasrecvd := false;
{$IFC DEBUG}
		WriteLn('tcp_process: about to dispose, avail = ',avail:1);
{$ENDC}
		ticks := tickcount;
		FOR i := 0 TO avail DO BEGIN
			CALL1C(circbuf[(taken+1+i) mod BUFSIZE],tc_dispose);
			if tickcount > ticks then begin
				SystemTask;
				ticks := tickcount;
				end;
			END;

		FOR i := maxchunks DOWNTO 0 DO BEGIN
			if free[i] THEN
				BEGIN
				if i = maxchunks then maxchunks := maxchunks - 1;
				cycle;
				END;
			first[i] := first[i] - (avail + 1);
			last[i]	 := last[i] - (avail + 1);
			END;

		if fin_rcvd THEN fin_offset := (fin_offset - avail) - 1;

		taken := (taken + avail + 1) mod BUFSIZE;

		if fin_rcvd THEN BEGIN
			if fin_offset = -1 THEN BEGIN
				{ foreign close request prompts
				  connection state change }
				CASE (conn_state) OF
				ESTAB:	        BEGIN
						conn_state := FINRCVD;
						otp^.tc_fin := true;
						conn_state := R_AND_S;
						END;
				FINSENT:        conn_state := SIMUL;
				FINACKED:       conn_state := TIMEWAIT;
{$IFC DEBUG}
				OTHERWISE       BEGIN
						WriteLn('TCP_RCV: bad state: ',
							conn_state:1);
						END;
{$ENDC}
				END; { end of CASE }

				{ ack foreign close request }
				{ fin's take up seq. no. space }
				otp^.tc_ack := otp^.tc_ack + 1;
				tcpbrcv := tcpbrcv + 1;
				send_wake(connection);
				END;
			END;

		otp^.tc_ack := otp^.tc_ack + avail + 1;
		tcpbrcv := tcpbrcv + avail + 1;
		otp^.tc_win := otp^.tc_win - (avail + 1);
		if (otp^.tc_win < TelLowWin) | (NOT in_more)
			THEN send_wake(connection);
		avail := -1;

		{ make sure to avoid silly window syndrome }
		{ this used to be done in tcp_send, but if there was
		  nothing to send, TCP would stop accepting data until
		  there was something to send! }
		if { (NOT still_data) & } (otp^.tc_win < TelLowWin) THEN
			otp^.tc_win := TelWin;

		Fake := tm_clear(tmack);
		dally_time := ack_time - TickCount;
		if dally_time > 0 THEN
			tm_tset(dally_time, @tcp_ack, POINTER(ORD4(connection)),
				tmack)
		ELSE send_wake(connection);
		END;
	end; { WITH }
	goto 666;
END; { tcp_process }

PROCEDURE send_wake(connection:Ref_tcconn);
BEGIN
	connection^.mustsend := TRUE;
	tk_wake(global^.TCPSend);
END;

{ This routine forms the main body of the TCP data sending task.  This task
  is awakened for one of two reasons: someone has data to send, or a resend
  timeout has occurred and a retransmission is called for.
  This routine in either case finishes filling in the header of the
  output packet, and calls in_write to send it to the net. }

PROCEDURE tcp_send(NoArg:PTR);
LABEL   666;
VAR
	sndlen: Integer;		 { bytes to send }
	Fake:Boolean;
	err:Integer;
	connection:Ref_tcconn;
BEGIN
	tk_block;       { Forget the initial wakeup }

	{ track through the connection queue looking for data to send }
666:    while true do begin
		connection := global^.tcpconns^.qHead;
		while connection <> NIL do
			if connection^.mustsend then leave
			else connection := connection^.tc_next;
		if connection = NIL then tk_block
		else leave;
		END;

	WITH connection^,global^ do begin

	connection^.mustsend := FALSE;

	if (conn_state = CLOSED) THEN GOTO 666;

	{ If this is not a timeout, clear the resend timer }

	if newsend THEN		        { if there is new data to send }
		BEGIN
		Fake := tm_clear(tcptm);
		tm_tset(retry_time, @tmhnd, POINTER(ORD4(connection)), tcptm);
		END;

	if resend <> 0 THEN		        { if resend timer has gone off }
		BEGIN
		tcpresend := tcpresend + 1;
		if resend >= 12 THEN BEGIN
			if (NOT_YET(conn_state, ESTAB)) THEN
				begin
				CALL(tc_tfcn);  { should be Boolean function }
				CALL(tc_cfcn);
				tcp_free(connection);
				goto 666;
				end
			else
				BEGIN
				NotResponding(StrCvt('TCP'));
				CALL(tc_cfcn);
				tcp_free(connection);
				goto 666;
				END;
			END
		else if (NOT_YET(conn_state, ESTAB)) THEN CALL(tc_rfcn);

		Fake := tm_clear(tcptm);
		tm_tset(retry_time,@tmhnd, POINTER(ORD4(connection)), tcptm)
		END; { if resend <> 0 then }

	{ Calculate the number of bytes to send, keeping in mind the
	  foreign host's window. If the connection isn't yet open,
	  send no data at all. }

	IF NOT_YET(conn_state,ESTAB) THEN sndlen := 0 ELSE sndlen := odlen;

	{ Finish filling in the TCP header }
	otp^.tc_thl := sizeof(tcp) div 4;
	ophp^.tp_len := sndlen + sizeof(tcp);
	otp^.tc_cksum := cksum(POINTER(ORD4(ophp)),sizeof(tcpph) div 2);
	otp^.tc_cksum := BitNOT(cksum(POINTER(ORD4(otp)),
				(sndlen+sizeof(tcp)+1) div 2));

{$IFC DEBUG}
	Write('Sending ');
	Print_TCP(otp);
{$ENDC}

	{ Send it }
	err := in_write(tcpfd, opbi, sndlen + sizeof(tcp), ForeignHost);
	if err <= 0 then begin
		Error(StrCvt('TELNET: Destination unreachable'));
		CALL(tc_cfcn);
		tcp_free(connection);
		goto 666;
		end;
	ack_time := TickCount + ACKDALLY;

	tcppsnt := tcppsnt + 1;
	tcpbsnt := otp^.tc_seq + sndlen;
	tcprack := tcpbrcv;

	if otp^.tc_rst THEN BEGIN	    { were we resetting? }
		cleanup(StrCvt('aborted'));
		CALL(tc_cfcn);
		tcp_free(connection);
		END
	else if conn_state = TIMEWAIT THEN  { if we sent the last ack }
		BEGIN
		CALL(tc_cfcn);
		tcp_free(connection);
		END;
	end; { with }
	GOTO 666;
END; { tcp_send }

{ Stuff a character into the send buffer for transmission, but do not
  wake up the TCP sending task.	 This assumes that more data will
  immediately follow. }

FUNCTION tc_put(connection:Ref_tcconn; c: char): Boolean;
VAR     RawPtr: PTR;
	tmp: byte;
BEGIN
	WITH connection^ do begin

	if NOT blk_inpt THEN BEGIN
		if (odlen >= MAXBUF) THEN BEGIN
			tc_put := TRUE;
			EXIT(tc_put);
			END
		else BEGIN
		RawPtr := POINTER(ORD4(odp)+odlen);
		RawPtr^ := byte(c);
		odlen := odlen + 1;
		RawPtr := POINTER(ORD4(odp)+odlen);
		RawPtr^ := 0;
			tc_put := FALSE;
			EXIT(tc_put);
		     END
		END
	else BEGIN
	    tc_put := TRUE;
	    EXIT(tc_put);
	    END;
	end;{with}
END; { tc_put }

{ Stuff a character into the send buffer for transmission, and wake
  up the TCP sender task to send it. }

FUNCTION tc_fput(connection:Ref_tcconn; c: char): Boolean;
VAR RawPtr: PTR;
BEGIN
	WITH connection^ do begin

	if(odlen >= MAXBUF) THEN BEGIN
		tc_fput := TRUE;
		EXIT(tc_fput);
		END;
	if NOT blk_inpt THEN BEGIN
		RawPtr := POINTER(ORD4(odp)+odlen);
		RawPtr^ := byte(c);
		odlen := odlen + 1;
		RawPtr := POINTER(ORD4(odp)+odlen);
		RawPtr^ := 0;
		END
	else BEGIN
		tc_fput := TRUE;
		EXIT(tc_fput);
		END;

	newsend := TRUE;
	send_wake(connection);
	tc_fput := FALSE;
	end;{with}
END; { tc_fput }

{ Indicate the presence of urgent data.	 Just sets the urgent pointer to
  the current data length and wakes up the sender. }

PROCEDURE tcpurgent(connection:Ref_tcconn);
BEGIN
	WITH connection^ do begin
	otp^.tc_urg := odlen;
	otp^.tc_furg := true;
	send_wake(connection);
	end;{with}
END; { tcpurgent }

PROCEDURE TCP_Win(connection:Ref_tcconn; win:Integer; lowwin: Integer);
BEGIN
	WITH connection^ do begin
	if (win<1) OR (win>TCPMAXWIND) OR (lowwin<1) OR (lowwin>TCPMAXWIND) THEN
		BEGIN
		TelWin := TCPWINDOW;
		TelLowWin := TCPLOWIND;
		END
	else BEGIN
		TelWin := win;
		TelLowWin := lowwin;
		END;
	end;{with}
END;

{ Actively open a tcp connection to foreign host fh on foreign socket
  fs.  Get a unique local socket to open the connection on.  Returns
  FALSE if unable to open an internet connection with the specified
  hosts and sockets, or TRUE otherwise.
  Note that this routine does not wait until the connection is
  actually opened before returning.  Instead, the user open function
  specified as ofcn in the call to tcp_init (which must precede
  this call) will be called when the connection is successfully opened. }

FUNCTION tcp_open(fh: Ref_in_name; fs: unsigned; ls: unsigned;
		   win:Integer; lowwin: Integer;
		   ofcn,infcn,yldfcn,cfcn,tmofcn,rsdfcn,buff:ProcPtr):Ref_tcconn;
{       *fh: in_name;}	       { foreign host address }
{       fs:unsigned;}	       { foreign socket }
{       ls:unsigned;}	       { local socket }
{       int     win;}	       { window to advertise }
{       int     lowwin;}       { low water mark for window }
{       int     (*ofcn)();}	        { user fcn to call on open done }
{       int     (*infcn)();}	        { user fcn to process input data }
{       int     (*yldfcn)();}	        { predicate set when user needs to run}
{       int     (*cfcn)();}	        { user fcn to call on close done }
{       int     (*tmofcn)();}	        { user fcn to call on icp timeout }
{       int     (*rsdfcn)();}	        { user icp resend function }
{       int     (*buff)();}	        { user output buffer room upcall }
VAR
	Dummy:Boolean;
	connection:Ref_tcconn;
BEGIN
	connection := tcp_alloc;
	if connection = NIL then
		begin
		CantAlloc(StrCvt('TCP'),StrCvt('connection'));
		tcp_open := NIL;
		exit(tcp_open);
		end;

	WITH connection^ do begin

	tc_ofcn := ofcn;		        { save user fcn addresses }
	tc_dispose := infcn;
	tc_yield := yldfcn;
	tc_cfcn := cfcn;
	tc_tfcn := tmofcn;
	tc_rfcn := rsdfcn;
	tc_buff := buff;

	ForeignHost := fh^;
	DestPort := fs;
	SourcePort := ls;
	if ls = 0 THEN BEGIN
		SourcePort := TickCount;
		if(SourcePort < 1000) THEN
		    SourcePort := SourcePort + 1000;
		END;
	TCP_Win(connection,win,lowwin);
	conn_state := SYNSENT;	        { syn-sent }

	ophp^.tp_src := in_mymach(fh^);
	ophp^.tp_dst := fh^;

	otp^.tc_srcp := ls;
	otp^.tc_dstp := fs;
	otp^.tc_win := TelWin;

	{ set resend timer }
	tm_tset(retry_time, @tmhnd, POINTER(ORD4(connection)), tcptm);
	newsend := TRUE;
	send_wake(connection);
	end;{with}
	tcp_open := connection;
END; { tcp_open }

{ Passive open; new in Mac version }

{$IFC LISTEN}
FUNCTION tcp_listen(ls: unsigned; win:Integer; lowwin: Integer;
		   ofcn,infcn,yldfcn,cfcn,tmofcn,rsdfcn,buff:ProcPtr):Ref_tcconn;
{       ls:unsigned;}	       { local socket }
{       int     win;}	       { window to advertise }
{       int     lowwin;}       { low water mark for window }
{       int     (*ofcn)();}	        { user fcn to call on open done }
{       int     (*infcn)();}	        { user fcn to process input data }
{       int     (*yldfcn)();}	        { predicate set when user needs to run}
{       int     (*cfcn)();}	        { user fcn to call on close done }
{       int     (*tmofcn)();}	        { user fcn to call on icp timeout }
{       int     (*rsdfcn)();}	        { user icp resend function }
{       int     (*buff)();}	        { user output buffer room upcall }
VAR
	connection:Ref_tcconn;
BEGIN
	connection := tcp_alloc;
	if connection = NIL then
		begin
		CantAlloc(StrCvt('TCP'),StrCvt('connection'));
		tcp_listen := NIL;
		exit(tcp_listen);
		end;

	WITH connection^ do begin

	tc_ofcn := ofcn;		        { save user fcn addresses }
	tc_dispose := infcn;
	tc_yield := yldfcn;
	tc_cfcn := cfcn;
	tc_tfcn := tmofcn;
	tc_rfcn := rsdfcn;
	tc_buff := buff;

	DestPort := 0;
	SourcePort := ls;
	TCP_Win(connection,win,lowwin);
	otp^.tc_win := TelWin;
	conn_state := LISTEN;
	end;{with}
	tcp_listen := connection;
END; { tcp_listen }
{$ENDC}

{ Display some tcp statistics and a few lines of unacked data. Should be
	revised and integrated in with the normal logging system. }

{ Changed this to go through the terminal emulator }

PROCEDURE tc_status(connection:Ref_tcconn);
LABEL   777;
VAR
	i:Integer;	 { loop index }
	crseen:Integer;
	RawPtr:PTR;
BEGIN
	if connection = NIL then exit(tc_status);

	WITH connection^,global^ do begin

	crseen := 0;
	EmStr('Connection State: ');

	CASE (conn_state) OF
	CLOSED:	   EmLn('Closed');
	SYNSENT:   EmLn('Trying to Open');
	SYNRCVD:   EmLn('Being Opened');
	ESTAB:	   EmLn('Established');
{$IFC LISTEN}
	LISTEN:	   EmLn('Listening for connections');
{$ENDC}
	OTHERWISE BEGIN
		EmStr('(');
		NumToStr(conn_state,Msg);
		EmStr(Msg);
		EmLn(') Closing');
		END;
	END;

	EmStr('Packets Sent: ');
	NumToStr(tcppsnt,Msg);EmStr(Msg);
	EmStr('; Packets Received: ');
	NumToStr(tcpprcv,Msg);EmLn(Msg);
	EmStr('Bytes Sent: ');
	NumToStr(tcpbsnt,Msg);EmStr(Msg);
	EmStr(' (Acked: ');
	NumToStr(otp^.tc_seq,Msg);EmStr(Msg);
	EmLn(')');
	EmStr('Bytes Received: ');
	NumToStr(tcpbrcv,Msg);EmStr(Msg);
	EmStr(' (Acked: ');
	NumToStr(tcprack,Msg);EmStr(Msg);
	EmLn(')');
	EmStr('Bad TCP xsums: ');
	NumToStr(bd_chk,Msg);EmStr(Msg);
	EmStr('; Window ignored: ');
	NumToStr(ign_win,Msg);EmLn(Msg);
	EmStr('Packets not for me: ');
	NumToStr(tcpsock,Msg);EmStr(Msg);
	EmStr('; Resends: ');
	NumToStr(tcpresend,Msg);EmStr(Msg);
	EmStr('; Rereceived: ');
	NumToStr(tcprercv,Msg);EmLn(Msg);
	EmStr('Local Win: ');
	NumToStr(otp^.tc_win,Msg);EmStr(Msg);
	EmStr('; Local Low Win: ');
	NumToStr(TelLowWin,Msg);EmStr(Msg);
	EmStr('; Foreign Win: ');
	NumToStr(frn_win,Msg);EmLn(Msg);

	EmStr('Ack #: ');
	NumToStr(otp^.tc_ack,Msg);EmStr(Msg);
	EmStr(', Seq #: ');
	NumToStr(otp^.tc_seq,Msg);EmLn(Msg);
	EmStr('Output Flags:');
	if(otp^.tc_syn) THEN  EmStr(' SYN');
	if(otp^.tc_fack) THEN EmStr(' ACK');
	if(otp^.tc_psh) THEN EmStr(' PSH');
	if(otp^.tc_furg) THEN EmStr(' URG');
	if(otp^.tc_fin) THEN EmStr(' FIN');
	if(otp^.tc_rst) THEN EmStr(' RST');
	EmLn('.');
	if(odlen <> 0) THEN
		EmLn('Output data:')
	else
		EXIT(tc_status);

	i:=0;
	while(true) DO BEGIN
		RawPtr := POINTER(ORD4(odp)+i);
		if RawPtr^ = $d THEN crseen := crseen + 1;
		if (RawPtr^ >= 32) AND (RawPtr^ < 127) then
			Em(chr(RawPtr^))
		else if RawPtr^ = $d then
			EmLn('')
		else
			EmStr('.');
		if crseen > 3
			THEN GOTO 777
			ELSE BEGIN
				i := i + 1;
				if (i > 100) OR (i > odlen)
					THEN GOTO 777
			     END;
		END;
	777:

	EmLn('');
	if i <= odlen THEN BEGIN
		EmLn('***MORE DATA***');
		END;
	end;{with}
	em_flush;
END; { tcp_status }

{ expedite (resend and push) a packet }
PROCEDURE tcp_ex(connection:Ref_tcconn);
BEGIN
	send_wake(connection);
	connection^.otp^.tc_psh := true;
END;

PROCEDURE tcp_ack(connection:Ref_tcconn);
BEGIN
	send_wake(connection);
END; { tcp_ack}

{ Handle a timer upcall. }
PROCEDURE tmhnd(connection:Ref_tcconn);
BEGIN
	connection^.resend := connection^.resend + 1;
	send_wake(connection);
END; { tmhnd }

FUNCTION tcp_sock: unsigned;
VAR     temp: LongInt;
BEGIN
	temp := TickCount;
	temp := BitAnd(temp,$0000ffff);
	if (temp < 1000) THEN temp := temp + 1000;
	tcp_sock := temp;
END; { tcp_sock}

{$IFC SAVEREST}

VAR
	SeqNum:Integer;
	AckNum:Integer;

{ restore a suspended tcp connection }
PROCEDURE tcp_restore;
BEGIN
	conn_state := ESTAB;

	tcpfd := in_open(TCPPROT, @tcp_rcv);
	if (tcpfd = NIL) THEN BEGIN
		CantConnect('tcp_restore','Internet');
		CALL(tc_cfcn);
		tcp_free(connection);
		EXIT(tcp_restore);
		END;

	ophp^.tp_src := in_mymach(ForeignHost);
	ophp^.tp_dst := ForeignHost;
	otp^.tc_srcp := SourcePort;
	otp^.tc_dstp := DestPort;
	otp^.tc_seq := SeqNum;
	otp^.tc_ack := AckNum-1;
	otp^.tc_win := TelWin;
	otp^.tc_bits := 0;
	newsend := TRUE;
	send_wake(connection);

	Write('foreign host ');
	out_inaddr(ForeignHost);
	WriteLn(', foreign port ',DestPort:1,', local port ',SourcePort:1);
	WriteLn('ack # is ',AckNum:1,' and seq # is ',SeqNum:1);
	CALL(tc_ofcn);
END; { tcp_restore }

FUNCTION tcp_save { : Boolean } ;
VAR     OSStatus: OSErr;
	FRN: Integer;
	RLength: LongInt;
	CR:CustRecord;
BEGIN
	OSStatus := FSOpen(CFileName, {Current Vol} 0, FRN);
	if( OSStatus <> noErr ) THEN
		tcp_save := FALSE
	else BEGIN
		RLength := sizeof(CustRecord);
		CR.SeqNum := otp^.tc_seq;
		CR.AckNum := otp^.tc_ack;
		CR.LocalIPAddr := LocalIPAddress;
		CR.GateWIPAddr := GWIPAddress;
		CR.NameServer := NSIPAddress[0];
		CR.TimeServer := TSIPAddress[0];
		CR.UseAB := Use_AB;
		CR.UserName := User_Name;
		CR.SourcePort := SourcePort;
		CR.DestPort := DestPort;
		CR.TelLowWin := TelLowWin;
		CR.ForeignHost := ForeignHost;
		{ Get to the start of the file }
		OSStatus := SetFPos(FRN,fsFromStart,0);
		IF OSStatus = noErr THEN
			OSStatus := FSWrite(FRN, RLength, @CR);
		OSStatus := FSClose(FRN);
		tcp_save := TRUE
	END
END; { tcp_save }

{$ENDC}

END.

!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting net/tftp_lib.text...
cat >net/tftp_lib.text <<'!E!O!F!'
{$X-}
{$M+}
{$R-}
{$0V-}
{$D-}
{$DECL DEBUG}
{$SETC DEBUG := false}
{$DECL ALLOCT}
{$SETC ALLOCT := false}
{$DECL BUNDLE}
{$SETC BUNDLE := true}
UNIT TFTP_Lib;

{ Please note the copyright notice in the file "copyright/notice" }

  { TFTP_LIB module using UDP over the Applebus		 }
  { by Mark Sherman (Dartmouth) and Tim Maroney (C-MU)	 }

INTERFACE

{$L-}
   USES {$U-}
      {$U Obj-Memtypes	  } Memtypes,
      {$U Obj-QuickDraw	  } QuickDraw,
      {$U Obj-OSIntf	  } OSIntf,
      {$U Obj-ToolIntf	  } ToolIntf,
      {$U Obj-ABPasIntf	  } ABPasIntf,
      {$U net-Task_Lib	  } Task_Lib,
      {$U net-Timer_Lib	  } Timer_Lib,
      {$U net-err_lib	  } Err_Lib,
      {$U net-IP_Lib	  } IP_Lib,
      {$U net-UDP_Lib	  } UDP_Lib,
      {$U net-calls	  } Call_Lib,
      {$U net-tftp_defs	  } TFTP_Defs,
      {$U net-tftp_file	  } TFTP_File;

{$L+}

CONST
	TFTKSZ = 2048;

PROCEDURE tftpinit;
PROCEDURE tfsinit(alert: ProcPtr; done:ProcPtr);
FUNCTION tftpuse(fhost: in_name; fname: StringPtr; volume:Integer;
		  rmfile: StringPtr; dir: integer; mode: integer;
		  userDone: ProcPtr): LongInt;
PROCEDURE tfs_on;
PROCEDURE tfs_off;
PROCEDURE tfabort;

IMPLEMENTATION

CONST MINTICKS = 10*60;

{ read or write request packet }
CONST
   MaxFileName = 512;
   tf_name_offset = 2;
TYPE
   tfreq = PACKED RECORD
	 {+0}	   tf_op: integer;	   { would be 1 (read) or 2 (write) }
	 {+2}	   tf_name: packed array[0..MaxFileName] of byte;
	END;
   Ref_tfreq = ^ tfreq;

{ data packet }

   tfdata = PACKED RECORD
	   { +0 }     tf_op: integer;	      { would be 3 }
	   { +2 }     tf_block: integer;
	   { +4 }     tf_data: packed array [0..511] of byte;
	END;
   Ref_tfdata = ^ tfdata;

{ finder information packet }
   tffinder = PACKED RECORD
	   { +0 }       tf_op: Integer;	     { would be 3 }
	   { +2 }       tf_block: Integer;   { would be 1 }
	   { +4 }       tf_finder: FInfo;
	 END;
     Ref_tffinder = ^ tffinder;

{ structure of an ack packet }

    tfack = PACKED RECORD
		tf_op: integer;	        { would be 4 }
		tf_block: integer;
	END;
    Ref_tfack = ^ tfack;

Ref_LongInt = ^ LongInt;

CONST
{ TFTP states }
	DATAWAIT        = 1;
	ACKWAIT	        = 2;
	DEAD	        = 3;
	TIMEOUT	        = 4;
	RCVERR	        = 5;
	RCVACK	        = 6;
	RCVDATA	        = 7;
	RCVLASTDATA     = 8;
	TERMINATED      = 9;

	TFTPSOCK        = 69    { TFTP's well known port };
	TFTPTRIES       = 10    { # of retries on packet transmission };
	REQTRIES        = 4     { # of retries on initial request };
	NORMLEN	        = 512   { normal length of received packet };

{  Constants for round trip time estimation and retry timeout.
    All calculation is done in clock ticks (at a rate of 18/second) but
    only the initial estimate and the upper limit are specified in ticks;
    the rest of the algorithm uses dimensionless multipliers.  }
{ Added multiplier of 4 for Macintosh (60 ticks/second) }

	Kinit = 3*4;    { Old = 3; Initial divisor for (1+1/K) estimate multiplier. }
	Kinc =  1;      { Reduce K by this if previous packet lost.  }
	T0    = 36;     { Initial value for round trip time estimate.  }

	MAXTMO = 4*216; { Old=216; upper limit on retry timeout timer, in ticks.}
	TMMULT =  3;    { multiplier to get retry timeout from round trip
			   estimate.  }

	OFF     = 0;
	ON      = 1;

	MAXTFTPS = 1;

VAR
	tfs_alert,server_done: ProcPtr;
	tftp: UDPCONN;
	tfstate: Byte;
	tfconnq:QHdr;	        { TFTP connection queue }

PROCEDURE tfrpyerr(udpc:UDPCONN; p: PACKET; code:Integer; text: StringPtr);
	FORWARD;
FUNCTION tfcleanup(cn: Ref_tfconn): LongInt;
	FORWARD;
PROCEDURE tfshnd(p:PACKET; len:Integer; host: in_name; Foo_Value:PTR);
	FORWARD;
PROCEDURE tftpwrit(cn: Ref_tfconn);
	FORWARD;
PROCEDURE tftpread(cn: Ref_tfconn);
	FORWARD;
PROCEDURE tftprcv(p:PACKET; len: Integer; fhost: in_name; cn: Ref_tfconn);
	FORWARD;
FUNCTION tfmkcn(dir:Integer; mode:Integer): Ref_tfconn;
	FORWARD;
PROCEDURE tfsndack(cn:Ref_tfconn; number: integer);
	FORWARD;
FUNCTION tfsndata(cn: Ref_tfconn; len: integer): Integer;
	FORWARD;
FUNCTION tfsndreq(cn: Ref_tfconn; fname: StringPtr): Integer;
	FORWARD;
FUNCTION tf_write(cn: Ref_tfconn; len: integer): Integer;
	FORWARD;
PROCEDURE tftptmo(cn: Ref_tfconn);
	forward;
PROCEDURE tfdoerr(cn: Ref_tfconn; p:PACKET; len:Byte);
	forward;

{$IFC DEBUG}
PROCEDURE tfcndump(cn: Ref_tfconn); FORWARD;

PROCEDURE out_inaddr(addr:in_name);
VAR tmp:STR255;
BEGIN
	cvt_inaddr(addr,tmp);
	Write(tmp);
END;
{$ENDC}

{$IFC ALLOCT}
PROCEDURE NoteAlloc(p:PACKET; s:STR255);
BEGIN
	WriteLn(s,': ',ORD4(p));
END;
{$ENDC}

{$S InitSeg}

PROCEDURE tftpinit;
BEGIN
	ntftps := 0;
	tfconnq.qFlags := 0;
	tfconnq.qHead := NIL;
	tfconnq.qTail := NIL;
END;

{$S TFTPSeg }

PROCEDURE tfkill(cn: Ref_tfconn);
VAR Dummy: Boolean;
BEGIN
    Dummy := tm_clear(cn^.tf_tm);
    cn^.tf_state := DEAD;
    tk_wake(cn^.tf_task);
END; { tfkill }

{ abort all TFTP connections }
PROCEDURE tfabort;
VAR     cn:Ref_tfconn;
	Dummy: Boolean;
BEGIN
	cn := POINTER(ORD4(tfconnq.qHead));
	while (cn <> NIL) do
		begin
		if cn^.tf_rcv >= 1 then begin
{$IFC DEBUG}
			WriteLn('tfabort: aborting connection in progress');
{$ENDC}
			tfudperr(cn^.tf_udp, cn^.tf_outp, ERRTXT,
				 StrCvt('Connection aborted'));
			tfkill(cn);
			end
		else begin
			{ I couldn't get this to work, so it's disallowed }
			Error(StrCvt(
				'Sorry, you can''t abort a connection request.'));
			end;
		cn := cn^.tf_next;
		end;
END;

FUNCTION tftp_data(p:Packet): PTR;
VAR Temp: Ref_tfdata;
BEGIN
    Temp := POINTER(ORD4(tftp_head(p)));
    tftp_data := POINTER( ORD4(temp) + { offset for tf_data } 4 );
END; { tftp_data }

{ turn the tftp server on}

PROCEDURE tfs_on;
BEGIN
	tfstate := ON;
END; { End of tfs_on }

{ turn the tftp server off }

PROCEDURE tfs_off;
BEGIN
	tfstate := OFF;
END; { end of tfs_off }

{ tfsinit(alert, done) - initialize the tftp server. This opens a UDP
	 connection but does not turn on the server. That needs to done by
	 an explicit call to tfs_on().
	alert() is a function which the server will call whenever it receives
	request for a transfer. This function will be called in the following
	way:
		alert(ip_addr, file_name, direction)

	alert() should return TRUE if it wishes to allow the transfer and
	FALSE otherwise.  done() is a function that the server will
	call to inform the invoker that this file transfer is complete or
	aborted.
}

PROCEDURE tfsinit(alert: ProcPtr; done:ProcPtr);
BEGIN
	tfstate := OFF;
	refusedt := 0; {time of most recent transfer refusal}

	tftp := udp_open(0, 0, TFTPSOCK, @tfshnd, NIL);
	IF (tftp=NIL) THEN
		Fatal(StrCvt('Can''t open UDP socket'),false);

	server_done := done;
	tfs_alert := alert;
END; { End of tfsinit }

{ handle an incoming tftp packet. This involves opening a udp connection
	(immediately so that we can report errors). If the server is OFF
	then the tftp will be refused; otherwise more checking will be done.
	Call the alert function and verify that the 'user' wishes to allow
	the tftp. Report an error if not. Finally, spawn a task to oversee
	the tftp and cleanup when it's done.
}

PROCEDURE tfshnd(p:PACKET; len:Integer; host: in_name; Foo_Value:PTR);
CONST
	Ignore_Case = FALSE;
	No_Diacrit = TRUE;
VAR
	nport: integer;
	ptreq: Ref_tfreq;
	cn: Ref_tfconn;
	mode: integer;
	FPart: tf_FilePart;
	smode,fname: STR255;
	FPartName: STRING[15];
	pup: Ref_udp;
	tmpudp: UDPCONN;
	TransDir: Integer;
	LongDummy: LongInt;
	OSStatus: OSErr;
{$IFC DEBUG}
	TempPtr: Ref_TFTP;
	TP2: Ref_Udp;
{$ENDC}

BEGIN
	CheckTask;

{$IFC DEBUG}
	WriteLn('tfshnd: entering tf server handler');
	WriteLn('Packet pointer: ',ORD4(p));
	WriteLn('Length	       : ',len);
	{ Write('Host	       : ');out_inaddr(host);WriteLn; }
	WriteLn('Useless data  : ',ORD4(Foo_Value));
	Write('TFTP Header at: ');
	TempPtr := tftp_head(p);
	IF TempPtr <> NIL THEN Write(ORD4(TempPtr)) ELSE Write(' NIL ');
	WriteLn('');
	IF TempPtr <> NIL THEN BEGIN
	    WriteLn('OPCde	 :',TempPtr^.tf_op);
	    WriteLn('Block	 :',TempPtr^.tf_block);
	    END;
	TP2 := udp_head(in_head(p));
	Write('UDP Header at : ');
	IF TP2 <> NIL THEN Write(ORD4(TP2)) ELSE Write(' NIL ');
	WriteLn('');
	IF TP2 <> NIL THEN BEGIN
	    WriteLn(' ud_srcp ',TP2^.ud_srcp);
	    WriteLn(' ud_dstp ',TP2^.ud_dstp);
	    WriteLn(' ud_len  ',TP2^.ud_len);
	    WriteLn(' ud_cksum',TP2^.ud_cksum);
	    END;
{$ENDC}

	pup := udp_head(in_head(p));

	{ If there is already a connection like this one, ignore duplicate
	  request. }

	if (udp_ckcon(host, pup^.ud_srcp) <> NIL) THEN BEGIN
{$IFC DEBUG}
		WriteLn('tfshnd: udp_ckcon <> NIL');
{$ENDC}
{$IFC ALLOCT}
		Write('tfshnd 1: ');
{$ENDC}
		udp_free(p);
		exit(tfshnd);
		END;

	{  If we refused a connection since this request got enqueued,
	assume this is a duplicate and discard it, so we don't bother the
	user with a duplicate question.	 (If we are unlucky, this might be
	a request from somewhere else that arrived while the user was
	thinking over the previous request.  Tough; somewhere else will
	just have to try again.)  }

	if (refusedt > p^.nb_tstamp) THEN BEGIN
{$IFC DEBUG}
		WriteLn('tfshnd: refusedt > p^.nb_tstamp');
{$ENDC}
{$IFC ALLOCT}
		Write('tfshnd 2: ');
{$ENDC}
		udp_free(p);
		exit(tfshnd);
		END;

	{  The next question:  Do we have room to do this transfer?  }

	if (ntftps >= MAXTFTPS) THEN BEGIN
		tk_yield;       { maybe the connection is waiting to die }
		if (ntftps >= MAXTFTPS) THEN BEGIN
			tfrpyerr(tftp, p, ERRTXT, StrCvt('Too many connections.'));
{$IFC ALLOCT}
			Write('tfshnd 3: ');
{$ENDC}
			udp_free(p);
			exit(tfshnd);
			END;
		END;

	ntftps := ntftps + 1;

	{  OK, let's check over the request more carefully. }

	ptreq := POINTER(ORD4(tftp_head(p)));
	ptreq^.tf_op := { bswap } (ptreq^.tf_op); { Swapping unnecssary on 68000 }
	IF (ptreq^.tf_op > WRQ) THEN BEGIN
{$IFC DEBUG}
		WriteLn('TFTP SERVER: bad tftp opcode ',ptreq^.tf_op);
{$ENDC}
{$IFC ALLOCT}
		Write('tfshnd 4: ');
{$ENDC}
		udp_free(p);
		ntftps := ntftps - 1;
		exit(tfshnd);
		END;

	{ Extract the file name from the request }
	CStr2PStr({ @ptreq^.tf_name} POINTER(ORD4(ptreq) + tf_name_offset )
		  ,@fname);

	{ Extract the transfer mode from the request }
	CStr2PStr(POINTER({Start of buffer } ORD4(ptreq) +
			  tf_name_offset +
			  {Characters of file name} Length(fname) +
			  {Null byte terminating fname} 1 ),
		  @smode);

	FPart := tf_DataPart; { We assume unless Mac mode }

	if EqualString(smode,'octet',Ignore_Case,No_Diacrit) then begin
		     mode := IMAGE;
		     FPart := tf_RsrcPart;
		     end
	else if EqualString(smode,'image',Ignore_Case,No_Diacrit) THEN begin
		     mode := IMAGE;
		     FPart := tf_RsrcPart;
		     end
	else if EqualString(smode,'netascii',Ignore_Case,No_Diacrit)
		THEN mode := ASCII
	else if EqualString(smode,'macintosh',Ignore_Case,No_Diacrit) THEN BEGIN
		mode := MACINTOSH;
		FPart := tf_FindPart;
		END
	else BEGIN
{$IFC DEBUG}
		Write('TFTP SERVER:  Bad mode ',smode,' in req ');
{$ENDC}
		tfrpyerr(tftp, p, ERRTXT, StrCvt('Bad mode'));
{$IFC ALLOCT}
		Write('tfshnd 6: ');
{$ENDC}
		udp_free(p);
		ntftps := ntftps - 1;
		exit(tfshnd);
		END;

	IF (tfstate=OFF) THEN BEGIN
		tfrpyerr(tftp, p, ERRTXT,
			 StrCvt('Transfers are not being accepted.'));
{$IFC ALLOCT}
		Write('tfshnd 7: ');
{$ENDC}
		udp_free(p);
		ntftps := ntftps - 1;
		exit(tfshnd);
		END;

	IF ptreq^.tf_op = RRQ
		THEN TransDir := PUT
		ELSE TransDir := GET;

	if CALL3A(host, @fname, TransDir, tfs_alert)=0 THEN
			BEGIN
			tfrpyerr(tftp, p, ERRTXT,StrCvt('Transfer refused.'));
			refusedt := TickCount;
{$IFC ALLOCT}
			Write('tfshnd 8: ');
{$ENDC}
			udp_free(p);
			ntftps := ntftps - 1;
			exit(tfshnd);
			END;

	{  It looks safe to try to open a connection.  }

	cn := tfmkcn(PUT, ASCII);       {  Direction is a dummy for now	 }
	if (cn=NIL) THEN BEGIN
		CantAlloc(StrCvt('TFTP'),StrCvt('connection'));
{$IFC ALLOCT}
		Write('tfshnd 9: ');
{$ENDC}
		udp_free(p);
		ntftps := ntftps - 1;
		exit(tfshnd);
		END;

	cn^.tf_done := server_done;
	cn^.tf_fn := fname;
	cn^.tf_fp := FPart;
	cn^.tf_volume := 0;
	cn^.tf_mode := mode;
	cn^.tf_udp := udp_open(host, pup^.ud_srcp,
				udp_socket, @tftprcv, POINTER(ORD4(cn)));
	if (cn^.tf_udp=NIL) THEN BEGIN
		CantConnect(StrCvt('TFTP'),StrCvt('UDP'));
		LongDummy := tfcleanup(cn);
{$IFC ALLOCT}
		Write('tfshnd 10: ');
{$ENDC}
		udp_free(p);
		ntftps := ntftps - 1;
		exit(tfshnd);
		END;

	IF (ptreq^.tf_op=RRQ) THEN BEGIN
		cn^.tf_dir := PUT;
		cn^.tf_fport := 1;
		cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
		cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
		cn^.tf_PB^.ioVersNum := 0;
		cn^.tf_PB^.ioPermssn := fsRdPerm; { Only need to read it }
		cn^.tf_PB^.ioMisc := NIL;

		CASE cn^.tf_fp OF
		    tf_DataPart:
			BEGIN
			OSStatus := PBOpen(cn^.tf_PB, FALSE);
			IF OSStatus = noErr THEN
				begin
				if ForkZero(cn) then OSStatus := EOFErr
				else begin
					cn^.tf_PB^.ioPosMode := fsFromStart;
					cn^.tf_PB^.ioPosOffset := 0;
					OSStatus := PBSetFPos(cn^.tf_PB,FALSE);
					end;
				end;
			END;

		    tf_RsrcPart: BEGIN
			     OSStatus := PBOpenRF(cn^.tf_PB, FALSE);
			     IF OSStatus = noErr THEN
				begin
				if ForkZero(cn) then OSStatus := EOFErr
				else begin
					cn^.tf_PB^.ioPosMode := fsFromStart;
					cn^.tf_PB^.ioPosOffset := 0;
					OSStatus := PBSetFPos(cn^.tf_PB,FALSE);
					end;
				end;
			     END;

		    tf_FindPart: BEGIN
			{ Just see if file exists, don't really need to open it }
			OSStatus := PBOpen(cn^.tf_PB, FALSE);
			IF OSStatus = noErr THEN
				OSStatus := PBClose(cn^.tf_PB,FALSE);
		    END;
		END; { end of case }

		if NOT OpenOK(cn,OSStatus,p) THEN BEGIN
			tfudperr(cn^.tf_udp, cn^.tf_outp, FNOTFOUND,
				 StrCvt('Could not open the file'));
			CALL1B(OFF, cn^.tf_done);
			LongDummy := tfcleanup(cn);
			exit(tfshnd);
			end;

		cn^.tf_task := tk_fork(tk_cur, @tftpread, TFTKSZ, 'tfrd',
					POINTER(ORD4(cn)));
		END
	else BEGIN
		cn^.tf_dir := GET;
		cn^.tf_fport := 1;
		cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
		cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
		cn^.tf_PB^.ioVersNum := 0;
		cn^.tf_PB^.ioPermssn := fsRdWrPerm;
		cn^.tf_PB^.ioMisc := NIL;
		{ See if file already there; yes, truncate it, else create it }
		CASE cn^.tf_fp OF

		tf_DataPart:
			BEGIN
			OSStatus := PBOpen(cn^.tf_PB,FALSE);
			ScratchIt(cn,OSStatus);
			END;

		tf_RsrcPart:
			BEGIN
			OSStatus := PBOpenRF(cn^.tf_PB, FALSE);
			ScratchIt(cn,OSStatus);
			END;

		tf_FindPart: BEGIN
		    { Do not need to open it, but if not available, it must
		      be created }
		    OSStatus := PBOpen(cn^.tf_PB,FALSE);
		    IF OSStatus = noErr THEN OSStatus := PBClose(cn^.tf_PB,FALSE)
		    ELSE IF OSStatus = fnfErr THEN BEGIN
			cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
			cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
			cn^.tf_PB^.ioVersNum := 0;
			OSStatus := PBCreate(cn^.tf_PB,FALSE);
			{ Finder info will be filled in later }
			END;
		    END; { end of finder part }

		END; { end of case statement }

		if NOT OpenOK(cn,OSStatus,p) then BEGIN
			tfudperr(cn^.tf_udp, cn^.tf_outp, ACCESS,
				 StrCvt('Could not open the file'));
			CALL1B(OFF, cn^.tf_done);
			LongDummy := tfcleanup(cn);
			exit(tfshnd);
			end;

		cn^.tf_task := tk_fork(tk_cur, @tftpwrit, TFTKSZ, 'tfwr',
				       POINTER(ORD4(cn)));
		tk_yield;   { let it get started }
		cn^.tf_expected := 1;
		tfsndack(cn, 0);
		END;

{$IFC ALLOCT}
	Write('tfshnd 13: ');
{$ENDC}
	udp_free(p);
END; { end of tfshnd }

{ User TFTP: attempt to transmit or receive a file in the specified mode. }

FUNCTION tftpuse(fhost: in_name; fname: StringPtr; volume:Integer;
		  rmfile: StringPtr; dir: integer; mode: integer;
		  userDone: ProcPtr): LongInt;
VAR
	cn: Ref_tfconn;
	mysock: integer;
	FIPtr: ^ FInfo;
	len: LongInt;
	Status: Integer;
	LongDummy: LongInt;
	OSStatus: OSErr;
BEGIN

{$IFC DEBUG}
	Write('TFTP_USER called on ');
	out_inaddr(fhost);
	WriteLn(fname^,',',rmfile^,',',dir,',',mode,'.');
{$ENDC}
	cn := tfmkcn(dir, mode);
	if(cn=NIL) THEN BEGIN
		CantAlloc(StrCvt('TFTP'),StrCvt('connection'));
		tftpuse := 0;
		EXIT(tftpuse);
		END;

	ntftps := ntftps + 1;

	cn^.tf_done := userDone;
	cn^.tf_fn := fname^;
	cn^.tf_volume := volume;
	cn^.tf_mode := mode;
	if mode = MACINTOSH then
		cn^.tf_fp := tf_FindPart
	else if mode in [IMAGE, OCTET] then
		cn^.tf_fp := tf_RsrcPart
	else
		cn^.tf_fp := tf_DataPart;

	if (dir=GET) THEN BEGIN
		if mode IN [IMAGE, ASCII, OCTET, MACINTOSH] THEN BEGIN
		    cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
		    cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
		    cn^.tf_PB^.ioVersNum := 0;
		    cn^.tf_PB^.ioPermssn := fsRdWrPerm;
		    cn^.tf_PB^.ioMisc := NIL;
		    { Try to open the file for writing, since we will try to
		      get this file }
		    CASE cn^.tf_fp OF

		    tf_DataPart:
			BEGIN
			OSStatus := PBOpen(cn^.tf_PB,FALSE);
			ScratchIt(cn,OSStatus);
			END;

		    tf_RsrcPart:
			BEGIN
			OSStatus := PBOpenRF(cn^.tf_PB, FALSE);
			ScratchIt(cn,OSStatus);
			END;

		    tf_FindPart: BEGIN
			OSStatus := PBOpen(cn^.tf_PB,false);
			IF OSStatus = noErr THEN
				OSStatus := PBClose(cn^.tf_PB,FALSE)
			ELSE IF OSStatus = fnfErr THEN BEGIN
				cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
				cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
				cn^.tf_PB^.ioVersNum := 0;
				OSStatus := PBCreate(cn^.tf_PB,FALSE);
				{ Finder info filled in later }
				END;
		       END; { end of finder part }
		    END; { end of CASE on file part }

		 END { end of ASCII, OCTET and IMAGE modes }
{$IFC DEBUG}
		else if (mode=TEST) THEN BEGIN
		    WriteLn('tftpuse: TEST mode not supported ');
		    LongDummy := tfcleanup(cn);
		    tftpuse := 0;
		    EXIT(tftpuse);
		    END
{$ENDC}
		else BEGIN
{$IFC DEBUG}
		    WriteLn('TFTP_USER: Bad mode ',mode,'.');
{$ENDC}
		    LongDummy := tfcleanup(cn);
		    tftpuse := 0;
		    EXIT(tftpuse);
		    END;

		if (OSStatus <> noErr) THEN BEGIN
			FOpenErr(fname,OSStatus);
			LongDummy := tfcleanup(cn);
			tftpuse := 0;
			EXIT(tftpuse);
			END;

		mysock := udp_socket;
		cn^.tf_udp := udp_open(fhost, TFTPSOCK, mysock, @tftprcv,
				POINTER(ORD4(cn)));
		if (cn^.tf_udp=NIL) THEN BEGIN
			CantConnect(StrCvt('TFTP'),StrCvt('UDP'));
			LongDummy := tfcleanup(cn);
			tftpuse := 0;
			EXIT(tftpuse);
			END;

		cn^.tf_task := tk_cur;
		cn^.tf_fport := 0;
		cn^.tf_expected := 1;
		cn^.tf_task := tk_fork(tk_cur, @tftpwrit, TFTKSZ, 'tfwr',
				       POINTER(ORD4(cn)));
		Status := tfsndreq(cn, rmfile);
		if Status < 0 then begin
			Error(StrCvt('TFTP: Destination unreachable'));
			LongDummy := tfcleanup(cn);
			tftpuse := 0;
			end
		else    tftpuse := 1;
		exit(tftpuse);
	END { end of GET test }

	else if (dir=PUT) THEN BEGIN
		if mode in [IMAGE, ASCII, OCTET, MACINTOSH] THEN BEGIN
		  cn^.tf_PB^.ioNamePtr := @cn^.tf_fn;
		  cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
		  cn^.tf_PB^.ioVersNum := 0;
		  cn^.tf_PB^.ioPermssn := fsRdPerm;
		  cn^.tf_PB^.ioMisc := NIL;
		  CASE cn^.tf_fp OF
		     tf_DataPart:
				{ Open a file for reading }
				begin
				OSStatus := PBOpen(cn^.tf_PB,FALSE);
				IF OSStatus = noErr THEN
					begin
					IF ForkZero(cn) THEN OSStatus := EOFErr
					else BEGIN
						cn^.tf_PB^.ioPosMode := fsFromStart;
						cn^.tf_PB^.ioPosOffset := 0;
						OSStatus := PBSetFPos(cn^.tf_PB,
								      FALSE);
						END;
					end;
				end;

			tf_RsrcPart:
			     BEGIN
			     OSStatus := PBOpenRF(cn^.tf_PB, FALSE);
			     IF OSStatus = noErr THEN
				begin
				IF ForkZero(cn) THEN OSStatus := EOFErr
				else BEGIN
					cn^.tf_PB^.ioPosMode := fsFromStart;
					cn^.tf_PB^.ioPosOffset := 0;
					OSStatus := PBSetFPos(cn^.tf_PB, FALSE);
					END;
				end;
			     END;

		     tf_FindPart: BEGIN
			{ Just see if file exists, don't really need to open it }
			OSStatus := PBOpen(cn^.tf_PB,FALSE);
			IF OSStatus = noErr THEN
				OSStatus := PBClose(cn^.tf_PB,FALSE);
			END; { end of finder part}
		  END; { end of case }
		  if NOT OpenOK(cn,OSStatus,NIL) then begin
			LongDummy := tfcleanup(cn);
			tftpuse := 0;
			exit(tftpuse);
			end;
		  END { end of mode test }
		else BEGIN
{$IFC DEBUG}
			WriteLn('Invalid mode for put.');
{$ENDC}
			LongDummy := tfcleanup(cn);
			tftpuse := 0;
			EXIT(tftpuse);
			END;

		if(OSStatus <> noErr) THEN BEGIN
			FOpenErr(fname,OSStatus);
			LongDummy := tfcleanup(cn);
			tftpuse := 0;
			EXIT(tftpuse);
			END;

		mysock := udp_socket;
		cn^.tf_udp := udp_open(fhost, TFTPSOCK, mysock, @tftprcv,
				       POINTER(ORD4(cn)));
		if (cn^.tf_udp=NIL) THEN BEGIN
			CantConnect(StrCvt('TFTP'),StrCvt('UDP'));
			OSStatus := PBClose(cn^.tf_PB,FALSE);
			LongDummy := tfcleanup(cn);
			tftpuse := 0;
			EXIT(tftpuse);
			END;

		cn^.tf_task := tk_cur;
		cn^.tf_expected := 0;
		cn^.tf_fport := 0;
		while cn^.tf_tries <> 0 do begin
			Status := tfsndreq(cn, rmfile);
			if Status < 0 then begin
				Error(StrCvt('TFTP: Destination unreachable'));
				LongDummy := tfcleanup(cn);
				tftpuse := 0;
				exit(tftpuse);
				end
			else    tftpuse := 1;
			cn^.tf_state := ACKWAIT;
			tk_block;
			if cn^.tf_state <> TIMEOUT then leave;
			end;
		if cn^.tf_tries = 0 then begin
			Error(StrCvt('TFTP: Request to send not acknowledged'));
			LongDummy := tfcleanup(cn);
			tftpuse := 0;
			exit(tftpuse);
			end
		else if cn^.tf_state = DEAD then begin
			LongDummy := tfcleanup(cn);
			tftpuse := 0;
			exit(tftpuse);
			end;

		cn^.tf_task := tk_fork(tk_cur, @tftpread, TFTKSZ, 'tfrd',
					POINTER(ORD4(cn)));
		tftpuse := 1;
		exit(tftpuse);
		END;
END; { end of tftpuse }

{ Task that receives a file from the remote system }

PROCEDURE tftpwrit(cn: Ref_tfconn);
VAR     Len: LongInt;
	Status: Integer;
	Dummy: Boolean;
BEGIN
	while(True) DO BEGIN
		cn^.tf_state := DATAWAIT;
		while (cn^.tf_state=DATAWAIT) DO tk_block;
{$IFC DEBUG}
		WriteLn('tftpwrit: awakened');
{$ENDC}

		if (cn^.tf_state=TIMEOUT) THEN BEGIN
			if (cn^.tf_tries > 0) THEN BEGIN
				Status := udp_write(cn^.tf_udp, cn^.tf_outp,
						    cn^.tf_lastlen);
				Dummy := tm_clear(cn^.tf_tm);
				tm_tset(cn^.tf_rt, @tftptmo,
					POINTER(ORD4(cn)), cn^.tf_tm);
				cycle;
				END
			ELSE BEGIN
				NotResponding(StrCvt('TFTP'));
				if cn^.tf_rcv >= NORMLEN then { in progress }
					tfudperr(cn^.tf_udp, cn^.tf_outp, ERRTXT,
					   StrCvt('Too many retries, giving up'));
				CALL1B(OFF, cn^.tf_done);
				len := tfcleanup(cn);
				ntftps := ntftps - 1;
				leave;
				END;
			END;

		if (cn^.tf_state = RCVLASTDATA) THEN BEGIN
			{ unless in Mac mode, have sent last data }
			if (cn^.tf_mode = MACINTOSH)
				AND (cn^.tf_fp <> tf_DataPart) THEN
				BEGIN
				cn^.tf_state := RCVDATA;
				if NOT MacPart(cn,1) then begin
					CALL1B(OFF, cn^.tf_done);
					Len := tfcleanup(cn);
					leave;
					end;
				END
			ELSE    BEGIN
				Dummy := tm_clear(cn^.tf_tm);
				CALL1B(ON, cn^.tf_done);
				Len := tfcleanup(cn);
				if (len=0) THEN
				 Error(StrCvt('Tried to transfer zero-length file'));
				ntftps := ntftps - 1;
				leave;
				END
			END;

		if (cn^.tf_state <> RCVDATA) THEN BEGIN
			Dummy := tm_clear(cn^.tf_tm);
			CALL1B(OFF, cn^.tf_done);
			len := tfcleanup(cn);
			ntftps := ntftps - 1;
			leave;
			END;
		END; { end of the WHILE loop }
	tk_exit;
END; { end of tfswrit }

{ Task that sends a file to the remote system }
{ This procedure was incorrect.	 It leaked storage and timed out improperly.
  It has been extensively rewritten. -- Tim }

PROCEDURE tftpread(cn: Ref_tfconn);
LABEL 666; { timeout retry }
VAR
	data: PTR;
	flen: LongInt;
	FIPtr: ^ FInfo;
	done: Boolean;
	pfill,psnt,tmp: PACKET;
	Status: Integer;
	LongDummy: LongInt;
	FinderInfo: FInfo;      { For macintosh mode transfers }
	OSStatus : OSErr;
	Dummy: Boolean;
	NEEDLF: Boolean;
BEGIN
	NEEDLF := FALSE;
	flen := NORMLEN;
	done := FALSE;
	cn^.tf_expected := 0;
	psnt := cn^.tf_outp;
	pfill := udp_alloc(512,0);
{$IFC ALLOCT}
	NoteAlloc('tftpread',pfill);
{$ENDC}
	if pfill = NIL then BEGIN
		CantAlloc(StrCvt('TFTP'),StrCvt('fill packet'));
		ntftps := ntftps - 1;
		tk_exit;
		end;

	{ Here's how the main loop for putting data blocks works:
		Fill the next packet to send
		Wait for ACK (of initial request or previous block)
		On timeout, resend and restart ACK wait, unless too many,
			in which case abort
		On ACK receipt, send the filled packet, reset the filled
			packet to be the old packet (which is no longer
			in use, having been ACKED), and go back up to Fill
			(unless done, in which case, wrap up [or restart if
			 in Mac mode])
	}

	while(true) DO BEGIN
		cn^.tf_state := ACKWAIT;
		CASE cn^.tf_fp of
		tf_DataPart, tf_RsrcPart:
			if cn^.tf_mode <> ASCII then begin
				cn^.tf_PB^.ioBuffer := tftp_data(pfill);
				cn^.tf_PB^.ioReqCount := NORMLEN;
				cn^.tf_PB^.ioPosMode := fsAtMark;
				OSStatus := PBRead(cn^.tf_PB,FALSE);
				flen := cn^.tf_PB^.ioActCount;
				end
			else begin
				{ ASCII mode; have to translate CR to CRLF }
				flen := NORMLEN;
				data := tftp_data(pfill);
				if NEEDLF then begin
					NEEDLF := false;
					data^ := $0a; { LF }
					flen := flen - 1;
					data := POINTER(ORD4(data)+1);
					end;
				cn^.tf_PB^.ioPosMode := $0d80;
				while flen > 0 do begin
					{ Read one line at a time }
					cn^.tf_PB^.ioBuffer := data;
					cn^.tf_PB^.ioReqCount := flen;
					OSStatus := PBRead(cn^.tf_PB,FALSE);
					flen := flen - cn^.tf_PB^.ioActCount;
					if OSStatus <> noErr then leave;
					data := POINTER(ORD4(data)
						     + (cn^.tf_PB^.ioActCount-1));
					if data^ = $0d { CR } then begin
						if flen = 0 then NEEDLF := true
						else begin
							data := POINTER(ORD4(data)
									+ 1);
							data^ := $0a; { LF }
							flen := flen - 1;
							data := POINTER(ORD4(data)
									+ 1);
							end;
						end;
					end; { while flen > 0 }
				flen := NORMLEN - flen;
				end; { ASCII mode }

		tf_FindPart: BEGIN
			IF (cn^.tf_expected = 0) THEN BEGIN
				FIPtr := POINTER(ORD4(tftp_data(pfill)));
				OSStatus := GetFInfo(cn^.tf_fn,cn^.tf_volume,FIPtr^);
				flen := sizeof(FInfo);
				END
			ELSE BEGIN
				flen := 0;
				OSStatus := eofErr;
				END;
			END;
		END; { end of the case }

		IF (OSStatus <> noErr) AND (OSStatus <> eofErr) THEN BEGIN
			{ For some reason our read died! }
			{ Just say that nothing is left and that EOF reached }
			FReadErr(StrCvt(cn^.tf_fn),OSStatus);
			OSStatus := eofErr;
			flen := 0;
			END;

		cn^.tf_size := cn^.tf_size + flen;

		if (cn^.tf_expected = 0) THEN cn^.tf_state := RCVACK;

666:	        while (cn^.tf_state=ACKWAIT) DO tk_block;

		if (cn^.tf_state=TIMEOUT) THEN BEGIN
			if (cn^.tf_tries > 0) THEN BEGIN
				Status := udp_write(cn^.tf_udp,psnt,
						    cn^.tf_lastlen);
				Dummy := tm_clear(cn^.tf_tm);
				tm_tset(cn^.tf_rt, @tftptmo,
					POINTER(ORD4(cn)), cn^.tf_tm);
				cn^.tf_state := ACKWAIT;
				goto 666;
				END
			ELSE BEGIN
				NotResponding(StrCvt('TFTP'));
				tfudperr(cn^.tf_udp,cn^.tf_outp,ERRTXT,
					 StrCvt('Retry limit exceeded, giving up'));
				cn^.tf_state := DEAD;
				CALL1B(OFF, cn^.tf_done);
				LongDummy := tfcleanup(cn);
{$IFC ALLOCT}
				WriteLn('tftpread 1: ');
{$ENDC}
				udp_free(pfill);
				ntftps := ntftps - 1;
				leave;
				END;
			END;

		if (cn^.tf_state=RCVACK) THEN BEGIN
			cn^.tf_expected := cn^.tf_expected + 1;

			if (done) THEN BEGIN
				if (cn^.tf_mode = MACINTOSH)
					AND (cn^.tf_fp <> tf_DataPart) THEN
					BEGIN
					if NOT MacPart(cn,0) then begin
						CALL1B(ON, cn^.tf_done);
						LongDummy := tfcleanup(cn);
						udp_free(pfill);
						leave;
						end;
					done := false;
					cycle;
					END
				ELSE    BEGIN
					Dummy := tm_clear(cn^.tf_tm);
					CALL1B(ON, cn^.tf_done);
					LongDummy := tfcleanup(cn);
{$IFC ALLOCT}
					WriteLn('tftpread 2: ');
{$ENDC}
					udp_free(pfill);
					ntftps := ntftps - 1;
					leave;
					END;
				END;

			{ Switch packets.  psnt is the last packet we sent;
			  it can now be safely written into (whereas before
			  it might have been waiting for an async net write).
			  So make it the fill packet. }
			tmp := psnt;
			cn^.tf_outp := pfill;
			psnt := pfill;
			pfill := tmp;
			Status := tfsndata(cn, flen);
			if (flen < NORMLEN) OR (OSStatus = eofErr) THEN
				done := TRUE;
			cycle;
			END;

		{ Anomalous state.  Die }
		Dummy := tm_clear(cn^.tf_tm);
		CALL1B(OFF, cn^.tf_done);
		LongDummy := tfcleanup(cn);
{$IFC ALLOCT}
		WriteLn('tftpread 3: ');
{$ENDC}
		udp_free(pfill);
		ntftps := ntftps - 1;
		leave;
		END;    { of tftpread main loop }
	tk_exit;
END { tftpread };

{ Utility routines }

{ Note: this formerly called udp_write, a no-no at timer level.	 The code
  that used it has been corrected. }

PROCEDURE tftptmo(cn: Ref_tfconn);
BEGIN
	cn^.tf_tmo := cn^.tf_tmo + 1;
	cn^.tf_tries := cn^.tf_tries - 1;
	cn^.tf_rsnd := cn^.tf_rsnd + 1;
	cn^.tf_NR := cn^.tf_NR + 1;
	cn^.tf_state := TIMEOUT;
	tk_wake(cn^.tf_task);
END; { end of tftptmo }

PROCEDURE tf_good(cn: Ref_tfconn);
VAR
	trtM: LongInt;
BEGIN
	trtM := TickCount - cn^.tf_sent;  {  Measured round trip time  }
	if(cn^.tf_NR_last=1) THEN cn^.tf_K := Kinit;

	if(cn^.tf_NR=1) THEN
		cn^.tf_trt := (trtM+cn^.tf_trt) DIV 2
	else BEGIN
		if ((cn^.tf_NR_last > 1) AND (cn^.tf_K >1) ) THEN
			cn^.tf_K := cn^.tf_K - Kinc;

		cn^.tf_trt := cn^.tf_trt + (cn^.tf_trt DIV cn^.tf_K);
		END;
	{ cn^.tf_rt := max(min( cn^.tf_trt*TMMULT , MAXTMO), MINTICKS); }
	cn^.tf_rt := cn^.tf_trt*TMMULT;
	if cn^.tf_rt > MAXTMO then cn^.tf_rt := MAXTMO;
	if cn^.tf_rt < MINTICKS then cn^.tf_rt := MINTICKS;

	cn^.tf_NR_last := cn^.tf_NR;
END; { end of tfgood }

{ Format up and send out an initial request for a tftp connection. }

FUNCTION tfsndreq(cn: Ref_tfconn; fname: StringPtr): Integer;
VAR
	ptreq : Ref_tfreq;
	NxtFieldPtr: PTR;
	modelen: Integer;
BEGIN
	ptreq := { (struct tfreq *)} POINTER(ORD4(tftp_head(cn^.tf_outp)));
	if (cn^.tf_dir=GET) THEN ptreq^.tf_op := RRQ
	else if (cn^.tf_dir=PUT) THEN ptreq^.tf_op := WRQ
	else BEGIN
{$IFC DEBUG}
		WriteLn('TFSNDREQ: Bad direction ',cn^.tf_dir,'.');
		tfcndump(cn);
		{ if BitAnd(NDEBUG,BUGHALT)<>0 THEN HALT; }
{$ENDC}
		tfsndreq := -1;
		exit(tfsndreq);
		END;

	PStr2CStr(fname,POINTER(ORD4(ptreq) + tf_name_offset));
	NxtFieldPtr := POINTER(ORD4(ptreq) + tf_name_offset + length(fname^) + 1);

	if (cn^.tf_mode=IMAGE) OR (cn^.tf_mode=TEST) THEN BEGIN
		PStr2CStr(StrCvt('image'),NxtFieldPtr);
		modelen := length('image');
		END
	else if (cn^.tf_mode=OCTET) THEN BEGIN
		PStr2CStr(StrCvt('octet'),NxtFieldPtr);
		modelen := length('octet');
		END
	else if (cn^.tf_mode=ASCII) THEN BEGIN
		PStr2CStr(StrCvt('netascii'),NxtFieldPtr);
		modelen := length('netascii');
		END
	else if (cn^.tf_mode=MACINTOSH) THEN BEGIN
		PStr2CStr(StrCvt('macintosh'),NxtFieldPtr);
		modelen := length('macintosh');
		END
	else BEGIN
{$IFC DEBUG}
		WriteLn('TFSNDREQ: Bad mode ',cn^.tf_mode,'.');
		tfcndump(cn);
		{ if BitAnd(NDEBUG,BUGHALT)<>0 THEN HALT; }
{$ENDC}
		tfsndreq := -1;
		exit(tfsndreq);
		END;

{$IFC DEBUG}
	WriteLn('TFTP:  sending file request');
{$ENDC}
	tfsndreq := tf_write(cn, { Overhead of tfreq for opcode }  2 +
				 { File name } length(fname^) + 1 +
				 { Mode size } modelen + 1);
END; { end of tfsndreq }

{ Process a data packet received for TFTP connection cn, according to the
	type specified in the connection block. Also
	handle out of sequence blocks and check on block length. If a block
	is way too short (len < tftp header size) send back an error message
	and abort the transfer; we have CSR disease. If the block is less
	than 512 bytes, shut down the transfer; we're done.
	Otherwise, just write it to disk (if necessary).
}

PROCEDURE tfdodata(cn: Ref_tfconn; p:PACKET; len: integer);
VAR
	data: PTR;
	ptfdat: Ref_tfdata;
	start_off, end_off: integer;
	RawPtr: PTR;
	Dummy: Boolean;
	OSStatus: OSErr;
	FIPtr: ^ FInfo;
	CurFInfo: FInfo;	        { Finder information of file }
BEGIN
	CheckTask;

	if(len < 4) THEN BEGIN
{	        log(tftplog, 'TFDODATA: CSR disease; len := %u', len);  }
		tfrpyerr(cn^.tf_udp, p, ERRTXT,StrCvt('You have CSR disease.'));
{$IFC DEBUG}
		WriteLn('TFDODATA: Died of CSR disease (gurgle).');
{$ENDC}
		tfkill(cn);
		exit(tfdodata);
		END;

	if (cn^.tf_state = TIMEOUT) THEN BEGIN
		{ Got it just in time! }
		cn^.tf_state := DATAWAIT;
		END
	else if (cn^.tf_state <> DATAWAIT) THEN BEGIN
{$IFC DEBUG}
		WriteLn('TFTP:  Received unexpected data block (state =',
			ORD(cn^.tf_state),')');
{$ENDC}
		tfrpyerr(cn^.tf_udp, p, ERRTXT,
			 StrCvt('Received unexpected data block'));
		exit(tfdodata);
		END;

	ptfdat := {(struct tfdata *)} POINTER(ORD4(tftp_head(p)));
	len := len - 4;			        { BAD. }

	if (ptfdat^.tf_block <> cn^.tf_expected) THEN BEGIN
{$IFC DEBUG}
		WriteLn('TFTP: Got block ', ptfdat^.tf_block,
			', expecting ', cn^.tf_expected);
{$ENDC}

{       We got a retransmission of a packet we have already tried to
ACK.  If we retransmit the ACK, and the old ACK finally gets through also,
our correspondent will resend the next data block, and we will do the same
thing for it, on through to the end of the file.  So we shouldn't retransmit
the ACK until we are convinced that the first one was actually lost.  We will
become convinced if our own timeout waiting for the next data packet
expires.  }

{  Here is what you shouldn't do. . .
		if(ptfdat^.tf_block=cn^.tf_expected-1)
			tfsndack(cn, ptfdat^.tf_block);
    And now we return to correct procedures. . .  }

		cn^.tf_ous := cn^.tf_ous + 1;
		exit(tfdodata);
		END;

	{ Send the ack before writing the data }
	Dummy := tm_clear(cn^.tf_tm);
	tf_good(cn);
	tfsndack(cn, ptfdat^.tf_block);
	cn^.tf_size := cn^.tf_size + len;
	data := tftp_data(p);

{$IFC DEBUG}
	WriteLn('tfdodata: about to write packet, len = ',len:1);
{$ENDC}
	IF cn^.tf_mode in [IMAGE, MACINTOSH, OCTET, ASCII] THEN BEGIN
	     CASE cn^.tf_fp OF
	       tf_DataPart, tf_RsrcPart:
		IF cn^.tf_mode <> ASCII THEN BEGIN
			cn^.tf_PB^.ioBuffer := data;
			cn^.tf_PB^.ioReqCount := len;
			cn^.tf_PB^.ioPosMode := fsAtMark;
			OSStatus := PBWrite(cn^.tf_PB,FALSE);
			END
		ELSE BEGIN
			{ ASCII mode, have to translate CRLF to just CR and
			  CR NUL to just CR as well; bare CR's shouldn't happen,
			  so we can just strip any character after a CR }
			if cn^.tf_SAWCR then begin
				{ last packet ended in CR, strip this character }
				cn^.tf_SAWCR := false;
				start_off := 1;
				end_off := 1;
{$IFC DEBUG}
				WriteLn('Last packet ended in CR');
{$ENDC}
				end
			else begin
				start_off := 0;
				end_off := 0;
				end;
			cn^.tf_PB^.ioPosMode := fsAtMark;
			OSStatus := noErr; { maybe no file write will be done }
{$IFC DEBUG}
			WriteLn('tfdodata: end_off = ',end_off:1,', start_off = ',
				start_off:1,', len = ',len:1);
{$ENDC}
			while end_off < len do begin
				RawPtr := POINTER(ORD4(data) + end_off);
				while (RawPtr^ <> $0d) & (end_off < len) do begin
					end_off := end_off + 1;
					RawPtr := POINTER(ORD4(RawPtr) + 1);
					end;
				if end_off = len then begin
					end_off := len - 1;
					RawPtr := POINTER(ORD4(RawPtr) - 1);
					end;
				cn^.tf_PB^.ioReqCount := (end_off - start_off) + 1;
				cn^.tf_PB^.ioBuffer
						:= POINTER(ORD4(data) + start_off);
{$IFC DEBUG}
				WriteLn('writing, ioReqCount = ',
					cn^.tf_PB^.ioReqCount:1);
{$ENDC}
				OSStatus := PBWrite(cn^.tf_PB, FALSE);
{$IFC DEBUG}
				WriteLn('written, OSStatus = ',OSStatus:1);
{$ENDC}
				if OSStatus <> noErr then leave;
				if (end_off = len-1) & (RawPtr^ = $0d) then begin
					{ last char in pkt was CR }
					cn^.tf_SAWCR := true;
					leave;
					end;
				start_off := end_off + 2;
				end_off := start_off;
				end; { while end_off < len do }
			END; { ASCII mode }

	      tf_FindPart: BEGIN
		      FIPtr := POINTER(ORD4(TFTP_Data(p)));
		      OSStatus := GetFInfo(cn^.tf_fn,cn^.tf_volume,CurFInfo);
		      CurFInfo.fdType := FIPtr^.fdType;
		      CurFInfo.fdCreator := FIPtr^.fdCreator;
		      { The ninth bit of the finder flags word means "the location
			of this file's icon is known".  However, the location
			is NOT known for a new file received by TFTP.  That
			bit has to be cleared, so that the Finder will figure out
			where the icon should go.  This is undocumented. }
		      CurFInfo.fdFlags := BitAND($feff,FIPtr^.fdFlags);
		      OSStatus := SetFInfo(cn^.tf_fn,cn^.tf_volume, CurFInfo);
		  END; { end of finder part }

	    END; { end of case statement }

	    IF OSStatus <> noErr THEN BEGIN
		    tfrpyerr(cn^.tf_udp, p, DISKFULL,StrCvt('Disk Full'));
		    FWriteErr(StrCvt(cn^.tf_fn),OSStatus);
		    tfkill(cn);
		    exit(tfdodata);
		    END;

	    END { end of mode test }
{$IFC DEBUG}
	else if cn^.tf_mode = TEST THEN BEGIN
	    WriteLn('TFDOData: Cannot process TEST mode');
	    END
{$ENDC}
	else BEGIN
		tfrpyerr(cn^.tf_udp, p, ERRTXT,StrCvt('Internal Error.'));
		tfkill(cn);
		exit(tfdodata);
		END;

	if (len=NORMLEN) THEN cn^.tf_state := RCVDATA
	else cn^.tf_state := RCVLASTDATA;

	cn^.tf_expected := cn^.tf_expected + 1;
	tk_wake(cn^.tf_task);
END; { End of tfdodata }

{ ack a certain block number }

PROCEDURE tfsndack(cn:Ref_tfconn; number: integer);
VAR     pack: Ref_tfack;
	Status: Integer;
BEGIN
	pack := { (struct tfack *)} POINTER(ORD4(tftp_head(cn^.tf_outp)));
	cn^.tf_lastlen := sizeof(tfack);
	pack^.tf_op := ACK;
	pack^.tf_block := number;
{$IFC DEBUG}
	WriteLn('TFTP:  ACKing block ',number);
{$ENDC}
	Status := tf_write(cn, sizeof(tfack));
END; { End of tfsndack }

{ write a tftp packet }

FUNCTION tf_write(cn: Ref_tfconn; len: integer):Integer;
VAR     mypacket: Ref_tfack;
BEGIN
	mypacket := { (struct tfack *) } POINTER(ORD4(tftp_head(cn^.tf_outp)));
	IF (mypacket^.tf_op <> RRQ) AND (mypacket^.tf_op <> WRQ) THEN BEGIN
		{ Byte swapping unnecessary on 68000 }
		{ mypacket^.tf_block := bswap (mypacket^.tf_block); }
		cn^.tf_tries := TFTPTRIES;
		END
		ELSE cn^.tf_tries := REQTRIES;

	mypacket^.tf_op := { bswap } (mypacket^.tf_op);
	cn^.tf_lastlen := len;
	cn^.tf_snt := cn^.tf_snt + 1;

	IF udp_write(cn^.tf_udp, cn^.tf_outp, len) <= 0 THEN begin
		tf_write := -1;
		cn^.tf_state := RCVERR;
		exit(tf_write);
		end;

	tm_tset(cn^.tf_rt, @tftptmo, POINTER(ORD4(cn)), cn^.tf_tm);
	cn^.tf_sent := TickCount;
	cn^.tf_NR := 1;
	tf_write := noErr;
END; { End of tf_write}

{$IFC DEBUG}

{ Dump a connection block for debugging purposes. }

PROCEDURE tfcndump(cn: Ref_tfconn);
BEGIN
	WriteLn('Connection addr = ',ORD4(cn));
	WriteLn('lastlen = ',cn^.tf_lastlen);
	WriteLn('expected =',cn^.tf_expected);

	WriteLn('state = ',cn^.tf_state);
	WriteLn(' dir = ',cn^.tf_dir);
	WriteLn(' mode = ',cn^.tf_mode);

	WriteLn('sent = ',cn^.tf_snt);
	WriteLn(' rcvd = ',cn^.tf_rcv);
	WriteLn(' tous = ',cn^.tf_ous);
	WriteLn(' tmo = ',cn^.tf_tmo);
	WriteLn(' rsnd = ',cn^.tf_rsnd);

	WriteLn('round trip delay = ',cn^.tf_trt);
	WriteLn(' K = ',cn^.tf_K);
	WriteLn(' curnt tmo = ',cn^.tf_rt);
END; { End of tfcndump }

{$ENDC}

{ Setup a TFTP connection block. }

FUNCTION tfmkcn(dir:Integer; mode:Integer): Ref_tfconn;
VAR
	cn: Ref_tfconn;
	Dummy: Boolean;
BEGIN
	cn := { (struct tfconn *)}
		POINTER(ORD4(NewPtr(sizeof(tfconn))));
	if (cn=NIL) THEN BEGIN
		CantAlloc(StrCvt('TFTP'),StrCvt('connection block'));
{	        log(tftplog, 'Couldn''t allocate connection block.');   }
		tfmkcn := NIL;
		exit(tfmkcn);
		END;

	cn^.tf_udp := NIL;
	cn^.tf_rcv := 0;
	cn^.tf_snt := 0;
	cn^.tf_ous := 0;
	cn^.tf_tmo := 0;
	cn^.tf_rsnd := 0;
	cn^.tf_dir := dir;
	cn^.tf_mode := mode;
	cn^.tf_size := 0;
	cn^.tf_K := Kinit;
	cn^.tf_trt := T0;
	cn^.tf_volume := 0;
	cn^.tf_SAWCR := false;
{$IFC DEBUG}
	cn^.tf_rt := 30*60; { 30 seconds }
{$ELSEC}
	{ cn^.tf_rt := max(min( cn^.tf_trt*TMMULT , MAXTMO), MINTICKS); }
	cn^.tf_rt := cn^.tf_trt*TMMULT;
	if cn^.tf_rt > MAXTMO then cn^.tf_rt := MAXTMO;
	if cn^.tf_rt < MINTICKS then cn^.tf_rt := MINTICKS;
{$ENDC}
	cn^.tf_NR := 0;
	cn^.tf_NR_last := 1;
	cn^.tf_tries := REQTRIES;

	cn^.tf_tm := tm_alloc;
	if(cn^.tf_tm=NIL) THEN BEGIN
		CantAlloc(StrCvt('TFTP'),StrCvt('timer'));
{	        log(tftplog, 'Couldn''t allocate timer.');      }
		DisposPtr(POINTER(ORD4(cn)));
		tfmkcn := NIL;
		exit(tfmkcn);
		END;

	cn^.tf_outp := udp_alloc(512, 0);
{$IFC ALLOCT}
	NoteAlloc(cn^.tf_outp, 'tfmkcn');
{$ENDC}
	if(cn^.tf_outp=NIL) THEN BEGIN
		CantAlloc(StrCvt('TFTP'),StrCvt('output packet'));
{	        log(tftplog, 'Couldn''t allocate output packet.');      }
		Dummy := tm_free(cn^.tf_tm);
		DisposPtr(POINTER(ORD4(cn)));
		tfmkcn := NIL;
		exit(tfmkcn);
		END;

	cn^.tf_PB := POINTER(ORD4(NewPtr(sizeof(ParamBlockRec))));
	if cn^.tf_PB = NIL then begin
		CantAlloc(StrCvt('TFTP'),StrCvt('parameter block'));
{	        log(tftplog, 'Couldn''t allocate parameter block.');	  }
		Dummy := tm_free(cn^.tf_tm);
		udp_free(cn^.tf_outp);
		DisposPtr(POINTER(ORD4(cn)));
		tfmkcn := NIL;
		exit(tfmkcn);
		END;

	cn^.tf_PB^.ioRefNum := -1;
	cn^.tf_task := tk_cur;
	cn^.tf_next := NIL;
	cn^.tf_qtype := dummyType;
	enqueue(POINTER(ORD4(cn)),@tfconnq);
	tfmkcn := cn;
END;

{ Cleanup routine called when done }

FUNCTION  tfcleanup(cn: Ref_tfconn): LongInt;
VAR
	size: LongInt;
	Dummy: Boolean;
	OSStatus: OSErr;
BEGIN
	{ Give us one last chance to flush out a packet that hasn't been
		processed yet. }
	tk_yield;

{$IFC DEBUG}
	WriteLn('TFCLEANUP called');
{$ENDC}
	if(cn^.tf_mode <> TEST) THEN BEGIN
		OSStatus := PBClose(cn^.tf_PB,FALSE);
		cn^.tf_PB^.ioNamePtr := NIL;
		cn^.tf_PB^.ioVRefNum := cn^.tf_volume;
		OSStatus := PBFlushVol(cn^.tf_PB,FALSE);
		END;
{$IFC BUNDLE}
	if (cn^.tf_dir = GET) AND (cn^.tf_mode in [IMAGE, OCTET]) then
		setbundle(StrCvt(cn^.tf_fn),cn^.tf_volume);
{$ENDC}
	udp_close(cn^.tf_udp);
	Dummy := tm_clear(cn^.tf_tm);
	Dummy := tm_free(cn^.tf_tm);
	DisposPtr(POINTER(ORD4(cn^.tf_PB)));
{$IFC ALLOCT}
	Write('tfcleanup: ');
{$ENDC}
	udp_free(cn^.tf_outp);
	cn^.tf_state := DEAD;
{$IFC DEBUG}
	tfcndump(cn);
{$ENDC}
{       tfcnlog(cn);    }
	size := cn^.tf_size;
	OSStatus := dequeue(POINTER(ORD4(cn)),@tfconnq);
	DisposPtr(POINTER(ORD4(cn)));
	tfcleanup := size;
END; { end of tfcleanup }

{ Send a TFTP data block. }

FUNCTION tfsndata(cn: Ref_tfconn; len: integer): Integer;
VAR
	tfdata_var: Ref_tfdata;
BEGIN
	tfdata_var := {(struct tfdata *)}
			POINTER(ORD4(tftp_head(cn^.tf_outp)));

	tfdata_var^.tf_op := DATA;
	tfdata_var^.tf_block := cn^.tf_expected;
{$IFC DEBUG}
	WriteLn('TFTP:  sending block ',tfdata_var^.tf_block:1,'.');
{$ENDC}
	tfsndata := tf_write(cn, sizeof(tfdata)-512+len);
END; { End of tfsndata }

{ Handle an incoming ack. }

PROCEDURE tfdoack(cn: Ref_tfconn; p: PACKET; len: Integer);
VAR
    ack: Ref_tfack;
    Dummy: Boolean;
BEGIN
	ack := { (tfack *) } POINTER(ORD4(tftp_head(p)));

	if(ack^.tf_block <> cn^.tf_expected) THEN BEGIN
			{  We have received an ACK,
			but not for the data block we sent.  It must be for
			a duplicate, since we wouldn't have sent
			the current data block if we hadn't gotten an ACK for
			the previous one.  This duplicate ACK means either
			that the network resent a packet that it wasn't sure
			got through, or else the other end resent the ACK
			because our current data block is lost or late.
			In either case, we can safely ignore this extra ACK,
			and if the ACK we want doesn't come our own timer will
			get us started again.  It isn't safe
			to resend the current data block now unless we are
			absolutely certain that the other end won't reack
			it if the earlier send was just delayed.  }

		cn^.tf_ous := cn^.tf_ous + 1;
{$IFC DEBUG}
		WriteLn('TFTP: ACK for block ',ack^.tf_block,' received again.');
{$ENDC}
		END
	else BEGIN
		tf_good(cn);
		Dummy := tm_clear(cn^.tf_tm);
		cn^.tf_state := RCVACK;
		tk_wake(cn^.tf_task);
		END;
END;

{ Handle an incoming packet. }
FUNCTION tfckport(cn: Ref_tfconn; p: PACKET): Boolean; FORWARD;

PROCEDURE tftprcv(p:PACKET; len: Integer; fhost: in_name; cn: Ref_tfconn);
VAR
	pdata: Ref_tfdata;
	op: integer;
	OSStatus:OSErr;
BEGIN
	CheckTask;

	cn^.tf_rcv := cn^.tf_rcv + 1;
	pdata := { (struct tfdata *)} POINTER(ORD4(tftp_head(p)));
	op := { bswap } (pdata^.tf_op); { swapping unnecessary for 68000 }
	pdata^.tf_op := op;

	CASE (op) OF

	DATA: if (tfckport(cn,p)) THEN tfdodata(cn, p, len);

	ACK: if (tfckport(cn,p)) THEN tfdoack(cn, p, len);

	ERRPCK: BEGIN
		if ((cn^.tf_fport=0)  OR
		    (cn^.tf_udp^.u_fport=udp_head(in_head(p))^.ud_srcp)) THEN
				BEGIN
				tfdoerr(cn, p, len);
				tfkill(cn);
				END;
{$IFC DEBUG}
		WriteLn('TFTP:  ignoring error packet.');
{$ENDC}
		END;

	OTHERWISE BEGIN
{$IFC DEBUG}
		WriteLn('TFTPRCV: Got bad opcode ',op,'.');
{$ENDC}
		tfrpyerr(cn^.tf_udp, p, ILLTFTP,StrCvt(' '));
		END;

	END; { End of case }

{$IFC ALLOCT}
	Write('tftprcv: ');
{$ENDC}
	udp_free(p);    {  So much for that packet!  }
END;	 {  end tftprcv }

{  Check over the port in the incoming packet.  }

FUNCTION tfckport(cn: Ref_tfconn; p: PACKET): Boolean;
VAR
	pdata: Ref_tfdata;
	svoutp: PACKET;
	pfport, svport: integer;
BEGIN
	pdata := {(struct tfdata *)} POINTER(ORD4(tftp_head(p)));
	pdata^.tf_block := { bswap } (pdata^.tf_block); { no byte swapping on 68000 }
	pfport := udp_head(in_head(p))^.ud_srcp;

	if(cn^.tf_fport=0) THEN BEGIN
	    {  Foreign port not yet identified, save it. }
	    if (cn^.tf_expected=pdata^.tf_block) THEN BEGIN{ but only if this is }
		cn^.tf_fport := 1;		     {  a response to our  }
		cn^.tf_udp^.u_fport := pfport;	     {  request.  }
		END
	    else BEGIN
{$IFC DEBUG}
		WriteLn('TFTP:  Received packet from old connection.');
		WriteLn('	    Expected block ',cn^.tf_expected,
			', got block ',pdata^.tf_block);
{$ENDC}
		tfrpyerr(cn^.tf_udp, p, ERRTXT,StrCvt('old connection'));
		tfckport := FALSE;
		EXIT(tfckport);
		END;
	    END { end of tf_fport = 0 test }
	else if( cn^.tf_udp^.u_fport <> pfport)	 THEN BEGIN
{$IFC DEBUG}
		    WriteLn('TFTP: Rcvd pkt from port ',pfport,', expect port ',
			    cn^.tf_udp^.u_fport);
{$ENDC}
		tfrpyerr(cn^.tf_udp, p, BADTID,StrCvt(' '));
		tfckport := FALSE;
		EXIT(tfckport);
		END;
	tfckport := TRUE;
END;    {  end tfckport	 }

{  Send error packet back where this packet came from.  }

PROCEDURE tfrpyerr(udpc:UDPCONN; p: PACKET; code:Integer; text: StringPtr);
VAR
	svport: integer;
	svhost: in_name;
	svoutp: PACKET;
BEGIN
	svport := udpc^.u_fport;  { Save correct port no.  }
	svhost := udpc^.u_fhost;  {  and host id  }
	udpc^.u_fport := udp_head(in_head(p))^.ud_srcp;	 { improper }
	udpc^.u_fhost := in_head(p)^.ip_src;  {	 layer violation  }
	tfudperr(udpc, p, code, text);

	udpc^.u_fport := svport; {N.B. udperr must not yield }
	udpc^.u_fhost := svhost; { or these saves will fail  }
END;    {  end tfrpyerr()  }

{ Process an incoming error packet }

PROCEDURE tfdoerr(cn: Ref_tfconn; p:PACKET; len:Byte);
CONST
     tf_err_offset = 4;
VAR     perr: PTR;
	LocalErr: STR255;
	i:INTEGER;
BEGIN
	perr := POINTER(ORD4(tftp_head(p)));
	CStr2PStr(POINTER(ORD4(perr) + tf_err_offset),@LocalErr);
	for i := 1 to length(LocalErr) do begin
		if (LocalErr[i] = chr($d)) or (LocalErr[i] = chr($a)) then
			LocalErr[i] := ' ';
		end;
	Error2(StrCvt('TFTP: Error from foreign host: '),@LocalErr);
	cn^.tf_state := RCVERR;
END; { end of tfdoerr }

END.

!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

I have posted the sources and binaries for the Macintosh Internet Protocols
to net.sources.mac .  The sources run about 12,000 lines of code, and
another few thousand lines of binhex'd binaries.  The source and binaries
are stored in twelve shell archive files; note that "sh" and not "csh"
should be used to unpack the archives, and that you should do the unpacking
in a new, clean directory.  The first archive file absolutely must be
unpacked first; after that, the order doesn't matter.

The Internet Protocols are network communication protocols defined by the
Department of Defense, in wide use around the world.  The Macintosh versions
run on Appletalk, the low-cost local area network for the Macintosh computer
(and, using a Centram board, for the IBM PC).  They are meant not so much
for Macintosh to Macintosh communication as for high-speed, error-checked
communication with minicomputers and mainframes, such as VAXen and TOPS-20
systems.  Some form of Seagate-compatible router will be needed in order to
use these programs as they are intended to be used.  (Seagate, the Stanford
Ethernet Appletalk Gateway, is a family of routers defined by compatible
software; the Seagate source code may be retrieved by FTP from SUMEX, in the
INFO-MAC archives.  Those of you who are not able to FTP should send mail to
Bill Croft, su-safe!croft, for information on the tape distribution.)

The programs included are TFTP (simple file transfer), TELNET (remote
terminal emulation), CUST (to customize your Macintosh's IP address and so
on), and SFMTEST, a test program provided as an illustration of the multiple
file "Get File" dialog.  Both sources and binaries are provided for all
these programs.

The sources may be of interest even to people who have no use for Appletalk,
because they contain software for emulating DEC vt100 terminal graphics,
error reporting to the user, running multiple tasks simultaneously (actually
synchronously) within an application, setting timers at a higher level than
direct manipulation of the vertical retrace queue, and an extended
"SFGetFile" (standard file package "Get File" dialog) that can be interfaced
to from any language without translating the source code, and which allows
multiple files to be selected.  The sources are in Lisa Pascal, but Lisa
Pascal can readily be transliterated into any of the Mac C compilers.

The code originated with the MIT PCIP network package for the IBM PC,
written in C.  Initial translation was done by Mark Sherman of Dartmouth; I
continued this work.  The final product is far more than a translation,
being thoroughly integrated into the weird and wonderful Macintosh
environment, and incorporating a set of, oh, roughly aleph-null bug fixes
and major extensions.

Second draft user-level documentation may be found in the file "user" in the
first file of the twelve files in the posting.  Better documentation is
currently being written; by someone else, since I have moved on to other
projects, such as LaserWriter print spooling on a UNIX machine.  There is
currently no external documentation of the source files, though they are
reasonably well commented.
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting sfmget/make.text...
cat >sfmget/make.text <<'!E!O!F!'
$EXEC
$SUBMIT t-assemble(sfmget-sfmget_asm)
$SUBMIT t-depend(sfmget-sfmgetfile,sfmget-sfmget_asm)
$SUBMIT t-assemble(sfmget-sfmintf_asm)
$SUBMIT t-depend(sfmget-sfmintf)
$SUBMIT t-depend(sfmget-sfmtest,sfmget-sfmintf,sfmget-sfmintf_asm)
$IF NEWER("sfmget-sfmtestr.text","sfmget-sfmtestr.rsrc") THEN
r{un}bin-RMaker
sfmget-sfmtestr
$ENDIF
$IF NEWER("sfmget-sfmgetr.text","sfmget-sfmgetr.rsrc") THEN
r{un}bin-RMaker
sfmget-sfmgetr
$ENDIF
$IF NEWER("sfmget-sfmgetfile.obj","sfmget-sfmgetfileL.obj") THEN
L{ink}?
+X
+R sfmhdr {partial link}
{ no more options}
sfmget-sfmget_asm
sfmget-sfmgetfile
obj-quickdraw
obj-tooltraps
obj-ostraps
obj-prlink
obj-packtraps
obj-rtlib
obj-pasinit
obj-paslibasm
obj-paslib
{no more object files}

sfmget-sfmgetfileL.OBJ
$ENDIF
$IF NEWER("sfmget-sfmtest.obj","sfmget-sfmtestL.obj") THEN
L{ink}?
+X
{ no more options}
sfmget-sfmtest
obj-quickdraw
obj-tooltraps
obj-ostraps
obj-prlink
obj-packtraps
obj-rtlib
obj-pasinit
obj-paslibasm
obj-paslib
obj-writelnwindow
sfmget-sfmintf
sfmget-sfmintf_asm
{no more object files}
{list on console}
sfmget-sfmtestL.OBJ    { output file is progL.OBJ }
$ENDIF
R{un}bin-rmaker { convert linked file to CODE resource file }
sfmget-sfmtesto
$
R{un}RFB       { Resource file builder }
sfmget-sfmtest.rsrc   { output file }
sfmget-sfmtestr.rsrc  { compiled resource file }
*
sfmget-sfmgetr.rsrc
*
sfmget-sfmtesto.rsrc  { CODE resource file }
*
{ no more input files }
{ quit }
R{un}bin-MacCom { write the disk }
RYFYLsfmget-sfmtest.RSRC
sfmtest
APPL	 { set type to APPL }
	 { set creator to ???? }
Y{es, bundle bit}E{ject}Q{uit}
$DOIT
$ENDEXEC

!E!O!F!
#
#
echo extracting sfmget/sfmget_asm.tex...
cat >sfmget/sfmget_asm.tex <<'!E!O!F!'
; header for Multiple File Get SOFT resource
     .PROC     sfmhdr
     .REF      GetFList,DelFList,SFMGetFile,CompFList,SortFList
     jmp       GetFList	      ; 0
     jmp       DelFList	      ; 4
     jmp       SFMGetFile     ; 8
     jmp       CompFList      ; 12
     jmp       SortFList      ; 16

     .PROC     CallBool
     MOVE.L    4(SP),A0	      ; Copy the procedure pointer
     MOVE.L    (SP)+,(SP)     ; Move the return address down
     JMP       (A0)	      ; Jump to the real routine

.END
!E!O!F!
#
#
echo extracting sfmget/sfmgetfile.tex...
cat >sfmget/sfmgetfile.tex <<'!E!O!F!'
{$X-}
{$U-}
{$R-}
{$D-}

Unit SFMGet;

INTERFACE

{$L-}
USES
    {$U Obj-Memtypes  } MemTypes,
    {$U Obj-QuickDraw } QuickDraw,
    {$U Obj-OSIntf    } OSIntf,
    {$U Obj-ToolIntf  } ToolIntf,
    {$U Obj-PackIntf  } PackIntf;
{$L+}

Type
	StrCell = Record
		str: StringHandle;
		selected: Boolean;
	end;

	StrList = Record
		howmany: Integer;
		volume:	 Integer;
		vname:	 String[27]; { change vn_offset if this changes place }
		table:	 Array [0..0] of StrCell;
	end;

	StrLPtr = ^StrList;
	StrLHandle = ^StrLPtr;

FUNCTION GetFList(vref:Integer; numTypes:Integer; typeList:SFTypeList;
		  fileFilter: ProcPtr):StrLHandle;
PROCEDURE DelFList(h:StrLHandle);
FUNCTION SFMGetFile(where: Point; fileFilter: ProcPtr;
		    numTypes:Integer; typeList:SFTypeList;
		    dlgHook:ProcPtr; flags:Integer): StrLHandle;
PROCEDURE CompFList(h:StrLHandle);
PROCEDURE SortFList(h:StrLHandle);

CONST   { constants to be OR'ed into flags word }
	SFMmulti = 1;
	SFMenable = 2;
	SFMstart = 4;

IMPLEMENTATION

CONST
    GrayLine = 9;
    VolName = 4;
    getHScroll = 11;
    getMChk = 12;
    shiftList = 255;
    IncHoriz = 8;

    DlogResID = -4001;	     {resource for the dialog}

    DefVCBPtr = $352;	     {address of default volume control block pointer}
    SFSaveDisk = $214;	     {SF package global: last colume seen}

    vn_offset = 4;

Type
	DrvElPtr = ^DrvQEl;
	VCBPtr = ^VCB;
	VCBHdl = ^VCBPtr;
	IntPtr = ^Integer;

	DlogRef = RECORD
		VBar:   ControlHandle;
		HBar:   ControlHandle;
		List:   StrLHandle;
		DrvPtr: DrvElPtr;
		filter: ProcPtr;
		oldVert:Integer;
		oldHoriz:Integer;
		Rows:   Integer;
		tnum:   Integer;
		tlist:  SFTypeList;
		tlist2: SFTypeList;
		tlist3: SFTypeList;
		tlist4: SFTypeList; { if ya want more'n 16 types, yer nuts }
		end;
	DlogRPtr = ^DlogRef;

PROCEDURE DrawBoxInerds(DlogPtr:DialogPtr);
VAR fi: FontInfo;
    i,v: Integer;
    invert: Rect;
    VBar,HBar: ControlHandle;
    h: StrLHandle;
    val: Integer;
    ItemType: INTEGER;
    ItemHdl: Handle;
    BoxRect: Rect;
    Clear: Rect;
Begin
  VBar := DlogRPtr(GetWRefCon(DlogPtr))^.VBar;
  HBar := DlogRPtr(GetWRefCon(DlogPtr))^.HBar;
  GetDItem(DlogPtr, getNmList, ItemType, ItemHdl, BoxRect);
  Clear := BoxRect;
  InsetRect(Clear,1,1);
  EraseRect(Clear);
  h := DlogRPtr(GetWRefCon(DlogPtr))^.List;
  if h = NIL then exit(DrawBoxInerds);
  GetFontInfo(fi);
  v := fi.ascent + boxRect.top + 1;
  val := GetCtlValue(VBar);
  for i := 0 to DlogRPtr(GetWRefCon(DlogPtr))^.rows do begin
	if i+val >= h^^.howmany then leave;
	MoveTo(boxRect.left+2 - (GetCtlValue(HBar)*IncHoriz),v);
	DrawString(h^^.table[i+val].str^^);
	if h^^.table[i+val].selected then begin
		SetRect(invert, boxRect.left+1, v - fi.ascent, boxRect.right - 1,
			v + fi.descent + fi.leading);
		InvertRect(invert);
		end;
	v := v + fi.ascent + fi.descent + fi.leading;
	end;
End;

PROCEDURE DrawGrayLine(theDialog: DialogPtr; ItemNo: integer);
{this is the UserItem procedure that draws the gray divider line}
VAR
   ItemType: integer;
   theItem:  Handle;
   itsRect:  Rect;
   penLoc:   Point;
Begin
  GetDItem(theDialog, ItemNo, ItemType, theItem, itsRect);
  penLoc.v := itsRect.top; penLoc.h := itsRect.left;
  PenNormal;
  moveto(penLoc.h, penLoc.v);
  while (penLoc.v < itsRect.bottom) do begin
	move(0,2);
	penLoc.v := penLoc.v + 2;
	Line(0,0);
	end;
End;

PROCEDURE PrVName(theDialog: DialogPtr; ItemNo: integer);
{this is the UserItem procedure that draws the volume name}
VAR
    ItemType:  integer;
    theItem:   Handle;
    itsRect:   Rect;
    TempClip:  RgnHandle;
    fi:	       FontInfo;
    width:     Integer;
    lastSpace: Integer;
    i,line2:   Integer;
    h:	       StrLHandle;
BEGIN
  GetDItem(theDialog, ItemNo, ItemType, theItem, itsRect);
  EraseRect(itsRect);
  h := DlogRPtr(GetWRefCon(theDialog))^.list;
  if h = NIL then exit(PrVName);
  GetFontInfo(fi);
  TempClip := NewRgn;
  GetClip(TempClip);
  ClipRect(itsRect);
  width := StringWidth(h^^.vname);
  if width < itsRect.right-itsRect.left then begin
	MoveTo(itsRect.left+(((itsRect.right-itsRect.left)-width) div 2),
		 itsRect.top + 3 + ((itsRect.bottom - itsRect.top) div 2));
	DrawString(h^^.vname);
	end
  else begin
	{ have to justify the sucker within the box }
	TextBox(Ptr(ORD4(h^)+vn_offset+1),length(h^^.vname),itsRect,teJustCenter);
	end;
  SetClip(TempClip);
  DisposeRgn(tempClip);
END;

PROCEDURE ScrollVert(DlogPtr: DialogPtr);
Var OldOrig:   integer;
    NewOrig:   integer;
    delta:     integer;
    theBox:    Rect;
    UpDateRgn: RgnHandle;
    TempClip:  RgnHandle;
    fi:	       FontInfo;
    SBar:      ControlHandle;
    ItemType:  INTEGER;
    ItemHdl:   Handle;
    BoxRect:   Rect;
    dp:	       DlogRPtr;
Begin
{get some new region space}
  UpDateRgn := NewRgn;		       {Rgn for use in updateing}
  TempClip  := NewRgn;		       {Rgn for use in restoring the clip}

  dp := DlogRPtr(GetWRefCon(DlogPtr));
  SBar := dp^.VBar;
  GetDItem(DlogPtr, getNmList, ItemType, ItemHdl, BoxRect);

{calculate the amount scrolled}
  OldOrig := dp^.oldVert;
  NewOrig := GetCtlValue(SBar);
  GetFontInfo(fi);
  {get V diff. between old & new origin}
  delta := (OldOrig - NewOrig) * (fi.ascent+fi.descent+fi.leading);

{get the area to scroll}
  SetRect(theBox, boxRect.left, boxRect.top, boxRect.right, boxRect.bottom);
  InSetRect(theBox, 1,1);		    {make scroll box smaller by one pixel}

{do the scrolling}
  ScrollRect(theBox, 0, delta, UpDateRgn);  {move pixels up by Vert. diff.}

{clip to the update region}
  GetClip(TempClip);
  ClipRect(UpDateRgn^^.rgnBBox);

{draw whatever is in the box}
  DrawBoxInerds(DlogPtr);

{restore the clip & remember the new origin for next scroll}
  SetClip(TempClip);
  dp^.oldVert := NewOrig;

{throw away unneeded things}
  DisposeRgn(tempClip);
  DisposeRgn(UpDateRgn);
End;

PROCEDURE ItemScroll(ScrollBarHdl:ControlHandle; CntlLoc: integer);
Begin
  If (CntlLoc = inDownButton) &
     (GetCtlValue(ScrollBarHdl) < GetCtlMax(ScrollBarHdl))
     then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) + 1)
  else If (CntlLoc = inUpButton) &
     (GetCtlValue(ScrollBarHdl) > GetCtlMin(ScrollBarHdl))
     then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) - 1);
  ScrollVert(ScrollBarHdl^^.contrlOwner);
End;

PROCEDURE PageVert(ScrollBarHdl:ControlHandle; CntlLoc: integer);
Begin
  If CntlLoc = inPageUp then
	  SetCtlValue(ScrollBarHdl,
		      GetCtlValue(ScrollBarHdl)
		      - DlogRPtr(GetWRefCon(ScrollBarHdl^^.contrlOwner))^.rows)
  Else If CntlLoc = inPageDown then
	  SetCtlValue(ScrollBarHdl,
		      GetCtlValue(ScrollBarHdl)
		      + DlogRPtr(GetWRefCon(ScrollBarHdl^^.contrlOwner))^.rows);
  If GetCtlValue(ScrollBarHdl) > GetCtlMax(ScrollBarHdl)
	  then SetCtlValue(ScrollBarHdl,GetCtlMax(ScrollBarHdl))
  Else If GetCtlValue(ScrollBarHdl) < GetCtlMin(ScrollBarHdl)
	  then SetCtlValue(ScrollBarHdl,GetCtlMin(ScrollBarHdl));
  ScrollVert(ScrollBarHdl^^.contrlOwner);
End;

{ Procedure to handle mouse-downs in the vertical scroll bar }
PROCEDURE VScroller(theDialog: DialogPtr);
Var
   BoxScrollBar: ControlHandle;
   mouseLoc:	 Point;
   ControlLoc:	 integer;
   dummy:	 integer;
Begin
{get the mouse location- its local to the current Grafport}
  GetMouse(mouseLoc);

{find the control part}
  ControlLoc := FindControl(mouseLoc, theDialog, BoxScrollBar);
  If ControlLoc <> 0 then begin
	Case ControlLoc of
	  inUpButtom:   dummy:=TrackControl(BoxScrollBar, mouseLoc, @ItemScroll);
	  inDownButton: dummy:=TrackControl(BoxScrollBar, mouseLoc, @ItemScroll);
	  inPageUp:     PageVert(BoxScrollBar, ControlLoc);
	  inPageDown:   PageVert(BoxScrollBar, ControlLoc);
	  inThumb:      If TrackControl(BoxScrollBar, mouseLoc, Nil) <> 0 then
			ScrollVert(theDialog);
	End;
	End
    else sysbeep(3);
End;

PROCEDURE ScrollHoriz(DlogPtr: DialogPtr);
Var OldOrig:   integer;
    NewOrig:   integer;
    delta:     integer;
    theBox:    Rect;
    UpDateRgn: RgnHandle;
    TempClip:  RgnHandle;
    SBar:      ControlHandle;
    ItemType:  INTEGER;
    ItemHdl:   Handle;
    BoxRect:   Rect;
    dp:	       DlogRPtr;
Begin
{get some new region space}
  UpDateRgn := NewRgn;		       {Rgn for use in updateing}
  TempClip  := NewRgn;		       {Rgn for use in restoring the clip}

  dp := DlogRPtr(GetWRefCon(DlogPtr));
  SBar := dp^.HBar;
  GetDItem(DlogPtr, getNmList, ItemType, ItemHdl, BoxRect);

{calculate the amount scrolled}
  OldOrig := dp^.oldHoriz;
  NewOrig := GetCtlValue(SBar);
  {get H diff. between old & new origin}
  delta := (OldOrig - NewOrig) * IncHoriz;

{get the area and scroll}
  SetRect(theBox, boxRect.left, boxRect.top, boxRect.right, boxRect.bottom);
  InSetRect(theBox, 1,1);		    {make scroll box smaller by one pixel}
  ScrollRect(theBox, delta, 0, UpDateRgn);

{clip to the update region}
  GetClip(TempClip);
  ClipRect(UpDateRgn^^.rgnBBox);

{draw whatever is in the box}
  DrawBoxInerds(DlogPtr);

{restore the clip & remember the new origin for next scroll}
  SetClip(TempClip);
  dp^.oldHoriz := NewOrig;

{throw away unneeded things}
  DisposeRgn(tempClip);
  DisposeRgn(UpDateRgn);
END;

{tracking routine for horizontal scoll bar}
PROCEDURE NameScroll(ScrollBarHdl:ControlHandle; CntlLoc: integer);
Begin
  If (CntlLoc = inDownButton) &
     (GetCtlValue(ScrollBarHdl) < GetCtlMax(ScrollBarHdl))
     then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) + 1)
  else If (CntlLoc = inUpButton) &
     (GetCtlValue(ScrollBarHdl) > GetCtlMin(ScrollBarHdl))
     then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) - 1);
  ScrollHoriz(ScrollBarHdl^^.contrlOwner);
End;

PROCEDURE PageHoriz(ScrollBarHdl:ControlHandle; CntlLoc: integer);
Begin
  If CntlLoc = inPageUp then
	  SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) - 5)
  Else If CntlLoc = inPageDown then
	  SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) + 5);
  If GetCtlValue(ScrollBarHdl) > GetCtlMax(ScrollBarHdl)
	  then SetCtlValue(ScrollBarHdl,GetCtlMax(ScrollBarHdl))
  Else If GetCtlValue(ScrollBarHdl) < GetCtlMin(ScrollBarHdl)
	  then SetCtlValue(ScrollBarHdl,GetCtlMin(ScrollBarHdl));
  ScrollHoriz(ScrollBarHdl^^.contrlOwner);
End;

{ Procedure to handle mouse down events in the horizontal scroll bar }
PROCEDURE HScroller(theDialog: DialogPtr);
Var
   HScrollBar:	 ControlHandle;
   mouseLoc:	 Point;
   ControlLoc:	 integer;
   dummy:	 integer;
Begin
{get the mouse location- its local to the current Grafport}
  GetMouse(mouseLoc);

{find the control part}
  ControlLoc := FindControl(mouseLoc, theDialog, HScrollBar);
  If ControlLoc <> 0 then begin
	Case ControlLoc of
	  inUpButtom,inDownButton:
			dummy := TrackControl(HScrollBar, mouseLoc, @NameScroll);
	  inPageUp,inPageDown:
			PageHoriz(HScrollBar, ControlLoc);
	  inThumb:      If TrackControl(HScrollBar, mouseLoc, Nil) <> 0 then
			ScrollHoriz(theDialog);
	End;
	End
    else sysbeep(3);
End;

PROCEDURE BoxContents(theDialog: DialogPtr; ItemNo: integer);
Var
   ItemType: integer;
   ItemHdl:  Handle;
   ItemRect: Rect;
   LongHdl:  LongInt;
   TempClip: RgnHandle;

Begin
{first get the Box size}
  GetDItem(theDialog, ItemNo, ItemType, ItemHdl, ItemRect);

{set the clip for drawing}
  TempClip := NewRgn;
  GetClip(TempClip);
  ClipRect(ItemRect);

{do the drawing}
  FrameRect(ItemRect);
  DrawBoxInerds(theDialog);

{restore the clip & clean up}
  SetClip(TempClip);
  DisposeRgn(tempClip);
End;

PROCEDURE ListAdjust(dlog: DialogPtr);
VAR i: Integer;
    anySelected: Boolean;
    h:StrLHandle;
    itemType: Integer;
    itemHandle: Handle;
    itemBox: Rect;
BEGIN
     h := DlogRPtr(GetWRefCon(dlog))^.List;
     anySelected := false;
     for i := 0 to h^^.howmany-1 do
	     if h^^.table[i].selected then anySelected := true;
     GetDItem(dlog,getOpen,itemType,itemHandle,itemBox);
     if anySelected then HiLiteControl(ControlHandle(itemHandle), 0)
     else HiLiteControl(ControlHandle(itemHandle), 255);
END;

PROCEDURE MultClick(dlog: DialogPtr; bar: ControlHandle; rows: Integer);
{ this procedure handles mouse-down events in the name box (with multi-selection)}
CONST pauseTime = 4; { 4 60th's of a second := 1/15 second }
VAR mouseLoc:	  Point;
    fontHeight:	  Integer;
    fi:		  FontInfo;
    invert:	  Rect;
    name_no:	  Integer;
    h:		  StrLHandle;
    ItemType:	  Integer;
    ItemHdl:	  Handle;
    BoxRect:	  Rect;
    time:	  LongInt;
    selecting:	  (yep, nope, dunno);
    val,max:	  Integer;
    lastDone:	  Integer;
BEGIN
	h := DlogRPtr(GetWRefCon(dlog))^.List;
	if h = NIL then begin
		sysbeep(3);
		exit(MultClick);
		end;
	GetFontInfo(fi);
	fontHeight := fi.ascent + fi.descent + fi.leading;
	GetDItem(Dlog, getNmList, ItemType, ItemHdl, BoxRect);
	selecting := dunno;
	max := GetCtlMax(bar);
	val := GetCtlValue(bar);
	while StillDown do begin { loop tracks mouse }
		SystemTask;
		GetMouse(mouseLoc);
		if mouseLoc.v < boxRect.top+1 then begin
			{ past the top of the first row, scroll down }
			If val > 0 then SetCtlValue(bar, val - 1)
			else cycle;
			val := val - 1;
			case selecting of
			dunno:  begin
				if h^^.table[val].selected
				then    selecting := nope
				else    selecting := yep;
				h^^.table[val].selected
					:= NOT h^^.table[val].selected;
				end;
			yep:    h^^.table[val].selected := true;
			nope:   h^^.table[val].selected := false;
			end;
			lastDone := val;
			ScrollVert(dlog);
			Delay(pauseTime,time);
			cycle;
			end
		else name_no := (mouseLoc.v - (boxRect.top + 1)) div fontHeight;
		if name_no > (rows - 1) then begin
			{ past the bottom of the last row, scroll upwards }
			If val < max then SetCtlValue(bar, val + 1)
			else cycle;
			val := val + 1;
			case selecting of
			dunno:  begin
				if h^^.table[val+(rows-1)].selected
				then    selecting := nope
				else    selecting := yep;
				h^^.table[val+(rows-1)].selected
					:= NOT h^^.table[val+(rows-1)].selected;
				end;
			yep:    h^^.table[val+(rows-1)].selected := true;
			nope:   h^^.table[val+(rows-1)].selected := false;
			end;
			lastDone := val+(rows-1);
			ScrollVert(dlog);
			Delay(pauseTime,time);
			cycle;
			end;
		if name_no + val >= h^^.howmany then cycle;
		case selecting of
		dunno:  begin
			if h^^.table[name_no + val].selected
			then    selecting := nope
			else    selecting := yep;
			h^^.table[name_no + val].selected
				:= NOT h^^.table[name_no + val].selected;
			invert.left := boxRect.left + 1;
			invert.top := boxRect.top + 1 + (name_no * fontHeight);
			invert.right := boxRect.right - 1;
			invert.bottom := boxRect.top+1+((name_no+1)*fontHeight);
			InvertRect(invert);
			end;
		yep:    begin
			if NOT h^^.table[name_no + val].selected then begin
				h^^.table[name_no + val].selected := true;
				invert.left := boxRect.left + 1;
				invert.top := boxRect.top+1+(name_no * fontHeight);
				invert.right := boxRect.right - 1;
				invert.bottom := boxRect.top+1+
						 ((name_no+1)*fontHeight);
				InvertRect(invert);
				end;
			end;
		nope:   begin
			if h^^.table[name_no + val].selected then begin
				h^^.table[name_no + val].selected := false;
				invert.left := boxRect.left + 1;
				invert.top := boxRect.top+1+(name_no*fontHeight);
				invert.right := boxRect.right - 1;
				invert.bottom := boxRect.top + 1 +
						 ((name_no+1)*fontHeight);
				InvertRect(invert);
				end;
			end;
		end; { case }
		lastDone := name_no + val;
		end; { loop }
	ListAdjust(dlog);
END;

PROCEDURE UnSelect(h:StrLHandle; val, rows, fontHeight:Integer; boxRect: Rect);
VAR i:INTEGER;
    invert:Rect;
BEGIN
	for i := 0 to h^^.howmany - 1 do begin
		if h^^.table[i].selected then begin
			h^^.table[i].selected := false;
			if (i >= val) & (i-val < rows) then begin
				SetRect(invert,
					boxRect.left + 1,
					boxRect.top + 1 + ((i - val) * fontHeight),
					boxRect.right - 1,
					boxRect.top+1+ (((i-val)+1)*fontHeight));
				InvertRect(invert);
				end;
			end;
		end;
END; {UnSelect}

PROCEDURE SingClick(dlog: DialogPtr; bar: ControlHandle; rows: Integer);
{ this procedure handles mouse-down events in the name box (no multi-selection)}
CONST pauseTime = 4; { 4 60th's of a second := 1/15 second }
VAR mouseLoc:	  Point;
    fontHeight:	  Integer;
    fi:		  FontInfo;
    invert:	  Rect;
    name_no:	  Integer;
    h:		  StrLHandle;
    ItemType:	  Integer;
    ItemHdl:	  Handle;
    BoxRect:	  Rect;
    time:	  LongInt;
    val,max:	  Integer;
    j:		  Integer;
BEGIN
	h := DlogRPtr(GetWRefCon(dlog))^.List;
	if h = NIL then begin
		sysbeep(3);
		exit(SingClick);
		end;
	GetFontInfo(fi);
	fontHeight := fi.ascent + fi.descent + fi.leading;
	GetDItem(Dlog, getNmList, ItemType, ItemHdl, BoxRect);
	max := GetCtlMax(bar);
	val := GetCtlValue(bar);
	{ single click unselects any existing selections }
	Unselect(h,val,rows,fontHeight,boxRect);
	while StillDown do begin { loop tracks mouse }
		SystemTask;
		GetMouse(mouseLoc);
		if mouseLoc.v < boxRect.top+1 then begin
			{ past the top of the first row, scroll down }
			val := GetCtlValue(bar);
			If val > 0 then begin
				if not h^^.table[val - 1].selected then
					Unselect(h,val,rows,fontHeight,boxRect);
				SetCtlValue(bar, val - 1);
				end
			else cycle;
			h^^.table[val - 1].selected := true;
			ScrollVert(dlog);
			Delay(pauseTime,time);
			cycle;
			end
		else name_no := (mouseLoc.v - (boxRect.top + 1)) div fontHeight;
		if name_no > (rows - 1) then begin
			{ past the bottom of the last row, scroll upwards }
			val := GetCtlValue(bar);
			If val < max then begin
				if not h^^.table[val + 1 + (rows-1)].selected then
					Unselect(h,val,rows,fontHeight,boxRect);
				SetCtlValue(bar, val + 1);
				end
			else cycle;
			h^^.table[val + 1 + (rows-1)].selected := true;
			ScrollVert(dlog);
			Delay(pauseTime,time);
			cycle;
			end;
		val := GetCtlValue(bar);
		if name_no + val >= h^^.howmany then cycle;
		if NOT h^^.table[name_no + val].selected then begin
			Unselect(h,val,rows,fontHeight,boxRect);
			h^^.table[name_no + val].selected := true;
			SetRect(invert,
				boxRect.left + 1,
				boxRect.top + 1 + (name_no * fontHeight),
				boxRect.right - 1,
				boxRect.top+1+((name_no+1)*fontHeight));
			InvertRect(invert);
			end;
		end; { loop }

	{ disable or enable Open button as needed }
	GetDItem(dlog,getOpen,ItemType,ItemHdl,BoxRect);
	for j := 0 to h^^.howmany-1 do
		if h^^.table[j].selected then begin
			HiLiteControl(ControlHandle(ItemHdl),0);
			exit(SingClick);
			end;
	HiLiteControl(ControlHandle(ItemHdl),255);
END;

FUNCTION CallBool(PB:ParmBlkPtr; func:ProcPtr):Boolean;
	external;

FUNCTION GetFList(vref:Integer; numTypes:Integer; typeList:SFTypeList;
		  fileFilter: ProcPtr):StrLHandle;
VAR
    i, j: Integer;
    h: StrLHandle;
    PB: ParamBlockRec;
    s: STR255;
    sh: StringHandle;
    err: OSErr;
    OK: Boolean;
    numFiles, badfiles, t: Integer;
    v: VCBPtr;
BEGIN
     if vref = 0 then begin { default volume, find its real reference number }
	  v := VCBHdl(DefVCBPtr)^;
	  vref := v^.vcbVRefNum;
	  end
     else if vref > 0 then begin { drive number, find volume ref }
	  v := VCBPtr(GetVCBQHdr^.qHead);
	  while (v <> NIL) & (v^.vcbDrvNum <> vref) do v := VCBPtr(v^.qLink);
	  if v = NIL then SysError(-56); { no such drive }
	  vref := v^.vcbVRefNum;
	  end
     else begin { volume reference number }
	  v := VCBPtr(GetVCBQHdr^.qHead);
	  while (v <> NIL) & (v^.vcbVRefNum <> vref) do v := VCBPtr(v^.qLink);
	  if v = NIL then SysError(-35); {no such volume}
	  end;

     numFiles := v^.vcbNmFls;
     h := StrLHandle(NewHandle(sizeof(StrList) + (numFiles*sizeof(StrCell))));
     if (memError <> noErr) | (h = NIL) then SysError(25);
     h^^.volume := vref;
     h^^.vname := v^.vcbVN;
     badfiles := 0;
     t := 0;
     for i := 1 to numFiles do begin
		PB.ioVersNum := 0;
		PB.ioVRefNum := vref;
		s := '';
		PB.ioNamePtr := @s;
		PB.ioFDirIndex := i;
		err := PBGetFInfo(@PB, FALSE);
		if err <> noErr then SysError(err);
		if numTypes <> -1 then begin
			OK := false;
			for j := 0 to numTypes-1 do
				if typeList[j] = PB.ioFlFndrInfo.fdType then begin
					OK := true;
					leave;
					end;
			end
		else OK := true;
		if OK & (fileFilter <> NIL) then
			OK := NOT CallBool(@PB,fileFilter);
		if OK then begin
		    sh := POINTER(ORD4(NewHandle(length(s)+1)));
		    if (memError <> noErr) | (sh = NIL) then SysError(25);
		    BlockMove(@s, Ptr(sh^), length(s) + 1);
		    h^^.table[t].str := sh;
		    h^^.table[t].selected := FALSE;
		    t := t + 1;
		    end
		else badfiles := badfiles + 1;
		end;
     h^^.howmany := numFiles - badfiles;
     SetHandleSize(Handle(h),sizeof(StrList) + (h^^.howmany*sizeof(StrCell)));
     err := MemError;
     if err <> noErr then SysError(err);
     GetFList := h;
END;

PROCEDURE DelFList(h:StrLHandle);
VAR i:INTEGER;
    err:OSErr;
BEGIN
	if h = NIL then exit(DelFList);
	for i := 0 to h^^.howmany-1 do begin
		if h^^.table[i].str <> NIL then begin
			DisposHandle(Handle(h^^.table[i].str));
			err := memError;
			if err <> noErr then SysError(err);
			end;
		end;
	DisposHandle(Handle(h));
	err := memError;
	if err <> noErr then SysError(err);
END;

PROCEDURE CompFList(h:StrLHandle);
VAR i,j:INTEGER;
    err:OSErr;
    selNum:Integer;
BEGIN
	if h = NIL then exit(CompFList);
	selNum := 0;
	for i := 0 to h^^.howmany-1 do begin
		if not h^^.table[i].selected then begin
			DisposHandle(Handle(h^^.table[i].str));
			err := memError;
			if err <> noErr then SysError(err);
			h^^.table[i].str := NIL;
			end
		else begin
			selNum := selNum + 1;
			{ move it back to fill up any holes }
			if (i > 0) & (h^^.table[i-1].str = NIL) then begin
				for j := i - 1 downto 0 do
					if h^^.table[j].str <> NIL then leave;
				if (j = 0) & (h^^.table[0].str = NIL) then j := -1;
				h^^.table[j+1].str := h^^.table[i].str;
				h^^.table[j+1].selected := true;
				h^^.table[i].str := NIL;
				h^^.table[i].selected := false;
				end;
			end;
		end;
	h^^.howmany := selNum;
	SetHandleSize(Handle(h),sizeof(StrList) + (h^^.howmany*sizeof(StrCell)));
END;

{simple selection sort is fast enough}
PROCEDURE SortFList(h:StrLHandle);
VAR     i:Integer;
	flag,tmp:Boolean;
	s:StringHandle;
BEGIN
	flag := true;
	while flag do begin
		flag := false;
		for i := 0 to h^^.howmany - 2 do begin
			if IUMagString(POINTER(ORD4(h^^.table[i].str^)+1),
				       POINTER(ORD4(h^^.table[i+1].str^)+1),
				       length(h^^.table[i].str^^),
				       length(h^^.table[i+1].str^^)) = 1 then begin
				flag := true;
				s := h^^.table[i].str;
				tmp := h^^.table[i].selected;
				h^^.table[i].str := h^^.table[i+1].str;
				h^^.table[i].selected := h^^.table[i+1].selected;
				h^^.table[i+1].str := s;
				h^^.table[i+1].selected := tmp;
				end;
			end;
		end;
END;

FUNCTION DrvHasDisk(Drive: DrvElPtr): Boolean;
VAR RawPtr:PTR;
BEGIN
	RawPtr := POINTER(ORD4(Drive)-3);
	if RawPtr^ in [1,2,8] then DrvHasDisk := true
	else DrvHasDisk := false;
END;

PROCEDURE DlogSetup(SFFileDlg:DialogPtr);
VAR
   ItemType:   integer;
   ItemHdl:    Handle;
   ItemRect:   Rect;
   VScrollBar: ControlHandle;
   HScrollBar: ControlHandle;
   List:       StrLHandle;
   rows:       INTEGER;
   dp:	       DlogRPtr;
   max,i,w:    Integer;
BEGIN
   dp := DlogRPtr(GetWRefCon(SFFileDlg));
   VScrollBar := dp^.VBar;
   HScrollBar := dp^.HBar;
   List := dp^.List;
   rows := dp^.Rows;

{set scroll bar values}
  if List = NIL then SetCtlMax(VScrollBar, 0)
  else begin
	if List^^.howmany >= rows then
	    SetCtlMax(VScrollBar,List^^.howmany-rows)
	else
	    SetCtlMax(VScrollBar,0);
	end;
  SetCtlValue(VScrollBar, 0);
  if GetCtlMax(VScrollBar) < 1 then HiLiteControl(VScrollBar,255)
  else HiLiteControl(VScrollBar,0);

{ the maximum value of the horizontal scroll bar is the maximum width of the
  strings in the file list minus the width of the name list box }
  SetCtlValue(HScrollBar,0);
  if List = NIL then HiLiteControl(HScrollBar,255)
  else begin
	max := 0;
	for i := 0 to List^^.howmany - 1 do begin
		w := StringWidth(List^^.table[i].str^^);
		if w > max then max := w;
		end;
	GetDItem(SFFileDlg,getNmList,itemType,itemHdl,itemRect);
	w := (itemRect.right - itemRect.left) + 4; { 4 for frame + offset }
	if max <= w then HiLiteControl(HScrollBar,255)
	else begin
		SetCtlMax(HScrollBar,((max - w) div IncHoriz) + 2);
		HiLiteControl(HScrollBar,0);
		end;
	end;

{make open button inactive initially}
  GetDItem(SFFileDlg,getOpen,itemType,itemHdl,itemRect);
  HiLiteControl(ControlHandle(itemHdl), 255);

{Set scrollbar old settings = to zero initially}
  dp^.oldVert := 0; dp^.oldHoriz := 0;
END;

FUNCTION SFFilter(theDialog: DialogPtr; VAR theEvent: EventRecord;
		  VAR theItem: INTEGER): Boolean;
VAR DIEvent:  EventRecord;
    DIpt:     Point;
    DIOK:     Boolean;
    ItemType: integer;
    ItemHdl:  Handle;
    ItemRect: Rect;
    ItemPt:   Point;
    dp:	      DlogRPtr;
    tmpDrvPtr:DrvElPtr;
    i:	      Integer;
BEGIN
	if GetNextEvent(diskMask,DIEvent) then begin
		dp := DlogRPtr(GetWRefCon(theDialog));
		if HiWord(DIEvent.message) <> noErr then begin
			DIPt.v := 120; DIPt.h := 150;
			if DIBadMount(DIpt, DIEvent.message) = noErr then
				DIOK := true
			else    DIOK := false;
			end
		else    DIOK := true;
		if DIOK then begin
			{ set up from new drive }
			DelFList(dp^.list);
			dp^.list := GetFList(LoWord(DIEvent.message),dp^.tnum,
					     dp^.tlist,dp^.filter);
			SortFList(dp^.list);
			DlogSetUp(theDialog);
			{ if 2 or more disks in, activate Drive button }
			i := 0;
			tmpDrvPtr := DrvElPtr(GetDrvQHdr^.qHead);
			while tmpDrvPtr <> NIL do begin
				if DrvHasDisk(tmpDrvPtr) then i := i + 1;
				tmpDrvPtr := DrvElPtr(tmpDrvPtr^.qLink);
				end;
			if i > 1 then begin
				GetDItem(theDialog,getDrive,ItemType,ItemHdl,
					 ItemRect);
				HiliteControl(ControlHandle(ItemHdl),0);
				end;
			{ activate Eject button }
			GetDItem(theDialog,getEject,ItemType,ItemHdl,ItemRect);
			HiliteControl(ControlHandle(ItemHdl),0);
			{ set drive pointer for later use }
			dp^.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead);
			while dp^.DrvPtr^.dQDrive <> LoWord(DIEvent.message) do
				dp^.DrvPtr := DrvElPtr(dp^.DrvPtr^.qLink);
			{ cause the dialog to be redrawn }
			InvalRect(theDialog^.portRect);
			end;
		end;

	if (theEvent.what = mouseDown) then begin
		GetDItem(theDialog,getNmList,ItemType,ItemHdl,ItemRect);
		ItemPt := theEvent.where;
		GlobalToLocal(ItemPt);
		if PtInRect(ItemPt,ItemRect) then begin
			if BitAnd(theEvent.modifiers,shiftKey) <> 0 then
				theItem := shiftList
			else    theItem := getNmList;
			SFFilter := true;
			exit(SFFilter);
			end;
		end;

	if (theEvent.what in [keyDown,autoKey])
	   & (BitAnd(theEvent.modifiers,BitOr(optionKey,cmdKey)) = 0)
	   & (chr(BitAnd(theEvent.message,$ff)) = chr($0d) { cr }) then begin
		GetDItem(theDialog,getOpen,ItemType,ItemHdl,ItemRect);
		ItemPt.h := (ItemRect.left + ItemRect.right) div 2;
		ItemPt.v := (ItemRect.top + ItemRect.bottom) div 2;
		if TestControl(ControlHandle(ItemHdl),ItemPt) in [254,0] then
			SFFilter := false
		else begin
			theItem := 1;
			SFFilter := true;
			end;
		end
	else SFFilter := false;
END;

FUNCTION SFMGetFile(where: Point; fileFilter: ProcPtr;
		    numTypes:Integer; typeList:SFTypeList;
		    dlgHook:ProcPtr; flags:Integer): StrLHandle;
Var
   SFFileDlg: DialogPtr;
   ItemHit:    integer;
   ItemType:   integer;
   ItemHdl:    Handle;
   ItemRect:   Rect;
   tmpRect:    Rect;
   DRec:       DlogRef;
   tmpDrvPtr:  DrvElPtr;
   tmp:	       Integer;
   fi:	       FontInfo;
   PB:	       ParamBlockRec;
   err:	       OSErr;
   savePort:   GrafPtr;
   v:	       VCBPtr;
   multichecked:Boolean;
   lastclick:  LongInt;
   lastpoint:  Point;
   tmppoint:   Point;
   ip:	       IntPtr;
Begin

  DRec.filter := fileFilter;
  DRec.tnum := numTypes;
  if numTypes > -1 then begin
	for tmp := 0 to numTypes - 1 do
		DRec.tlist[tmp] := typeList[tmp];
	end;
  lastclick := 0;

{create the dialog}
  GetPort(savePort);
  SFFileDlg := GetNewDialog(DlogResID, Nil, Pointer(-1));
  MoveWindow(SFFileDlg, where.h, where.v, FALSE);
  SetPort(SFFileDlg);
  SetWRefCon(SFFileDlg,ORD4(@DRec));
  GetDItem(SFFileDlg,getScroll,ItemType,ItemHdl,ItemRect);
  DRec.VBar := NewControl(SFFileDlg, ItemRect, '', TRUE, 0, 0, 0, scrollBarProc,0);
  GetDItem(SFFileDlg,getHScroll,ItemType,ItemHdl,ItemRect);
  GetDItem(SFFileDlg,getNmList,ItemType,ItemHdl,tmpRect);
  DRec.HBar := NewControl(SFFileDlg, ItemRect, '', TRUE, 0, 0,
			  tmpRect.right div IncHoriz, scrollBarProc,0);

{set up the multiple file check box using flags}
  GetDItem(SFFileDlg,getMChk,ItemType,ItemHdl,ItemRect);
  if BitAnd(flags,SFMmulti) = 0
  then HideControl(ControlHandle(ItemHdl))
  else if BitAnd(flags,SFMenable) = 0
  then HiLiteControl(ControlHandle(ItemHdl),255);
  if BitAnd(flags,SFMstart) = 0
  then multichecked := false
  else multichecked := true;
  SetCtlValue(ControlHandle(ItemHdl),ord(multichecked));

{set the font information}
  TextFont(0);
  TextFace([]);
  TextMode(srcOr);
  TextSize(12);
  GetFontInfo(fi);
  GetDItem(SFFileDlg, getNmList, ItemType, ItemHdl, ItemRect);
  Drec.rows := 1 + ((ItemRect.bottom-ItemRect.top) - 3)
		 div (fi.ascent+fi.descent+fi.leading);

{set up the drive button (oh, God, this is complicated...)}
  DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead);
  if DRec.DrvPtr^.qLink = NIL then begin { only one drive attached }
	GetDItem(SFFileDlg,getDrive,itemType,itemHdl,itemRect);
	HideControl(ControlHandle(itemHdl)); { hide Drive button }
	if DrvHasDisk(DRec.DrvPtr) then { if it has a disk, set up from that }
		DRec.List := GetFList(DRec.DrvPtr^.dQDrive,numTypes,typeList,
				      fileFilter)
	else begin { drive has no disk, disable Eject and set list to NIL }
		DRec.List := NIL;
		GetDItem(SFFileDlg,getEject,itemType,itemHdl,itemRect);
		HiLiteControl(ControlHandle(itemHdl), 255);
		end;
	end
  else begin {multiple drives in queue}
	tmp := 0;
	tmpDrvPtr := DrvElPtr(GetDrvQHdr^.qHead);
	while tmpDrvPtr <> NIL do begin { count number of drives with disks }
		if DrvHasDisk(tmpDrvPtr) then tmp := tmp + 1;
		tmpDrvPtr := DrvElPtr(tmpDrvPtr^.qLink);
		end;
	if tmp = 0 then begin { no drives have disks, disable Eject and Drive }
		DRec.List := NIL;
		GetDItem(SFFileDlg,getEject,itemType,itemHdl,itemRect);
		HiLiteControl(ControlHandle(itemHdl), 255);
		GetDItem(SFFileDlg,getDrive,itemType,itemHdl,itemRect);
		HiLiteControl(ControlHandle(itemHdl), 255);
		end
	else if tmp = 1 then begin { only one drive has a disk, disable Drive }
		{ find which drive it is that has a disk, set up from it }
		DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead);
		while NOT DrvHasDisk(DRec.DrvPtr) do
			DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink);
		Drec.List := GetFList(DRec.DrvPtr^.dQDrive,numTypes,typeList,
				      fileFilter);
		GetDItem(SFFileDlg,getDrive,itemType,itemHdl,itemRect);
		HiLiteControl(ControlHandle(itemHdl), 255);
		end
	else begin { multiple drives have disks }
		{ try to use last volume seen by SFGetFile }
		v := VCBPtr(GetVCBQHdr^.qHead);
		tmp := IntPtr(SFSaveDisk)^;
		tmp := -tmp;
		while (v <> NIL) & (v^.vcbVRefNum <> tmp) do
			v := VCBPtr(v^.qLink);
		if v <> NIL then begin
			DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead);
			while (DRec.DrvPtr <> NIL)
			      & (DRec.DrvPtr^.dQDrive <> v^.vcbDrvNum)
				do DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink);
			end
		else begin
			{ try to use default volume }
			v := VCBHdl(DefVCBPtr)^;
			DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead);
			while (DRec.DrvPtr <> NIL)
			      & (DRec.DrvPtr^.dQDrive <> v^.vcbDrvNum)
				do DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink);
			end;
		{ if those volumes don't have disks, then use the first
		  drive that does have a disk }
		if (DRec.DrvPtr = NIL) | NOT DrvHasDisk(DRec.DrvPtr) then begin
			DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead);
			while NOT DrvHasDisk(DRec.DrvPtr) do
				DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink);
			end;
		{ set up from whatever disk was chosen }
		Drec.List := GetFList(DRec.DrvPtr^.dQDrive, numTypes,
				      typeList, fileFilter);
		end;
	end;

  if Drec.List <> NIL then SortFList(Drec.List);

{set the gray line userItem}
  GetDItem(SFFileDlg, GrayLine, ItemType, ItemHdl, ItemRect);
  ItemHdl := Handle(ORD(@DrawGrayLine));
  SetDItem(SFFileDlg, GrayLine, ItemType, ItemHdl, ItemRect);

{set the Box UserItem}
  GetDItem(SFFileDlg, getNmList, ItemType, ItemHdl, ItemRect);
  ItemHdl := Handle(ORD(@BoxContents));
  SetDItem(SFFileDlg, getNmList, ItemType, ItemHdl, ItemRect);

{set the volume name UserItem}
  GetDItem(SFFileDlg, VolName, ItemType, ItemHdl, ItemRect);
  ItemHdl := Handle(ORD(@PrVName));
  SetDItem(SFFileDlg, VolName, ItemType, ItemHdl, ItemRect);

{set up control values and such}
  DlogSetUp(SFFileDlg);

{everything has been setup, show the dialog window}
  ShowWindow(SFFileDlg);

{draw the fake grow box}
  GetDItem(SFFileDlg, getScroll, ItemType, ItemHdl, ItemRect);
  tmpRect.top := ItemRect.bottom - 1; tmpRect.right := ItemRect.right;
  GetDItem(SFFileDlg, getHScroll, ItemType, ItemHdl, ItemRect);
  tmpRect.left := ItemRect.right - 1; tmpRect.bottom := ItemRect.bottom;
  FrameRect(tmpRect);
  InsetRect(tmpRect,2,2);
  while (tmpRect.right-tmpRect.left) >= 2 do begin
	FrameRect(tmpRect);
	InsetRect(tmpRect,2,2);
	end;

{now start processing some user inputs}
  Repeat
    ModalDialog(@SFFilter, ItemHit);
    Case ItemHit of
	getEject:       begin
			PB.ioNamePtr := NIL;
			PB.ioVRefNum := DRec.List^^.volume;
			err := PBEject(@PB);
			if err = noErr then begin
				DelFList(DRec.List);
				{ try to move on to next drive.	 However, all the
				  drives may be empty. }
				DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink);
				while (DRec.DrvPtr <> NIL)
				      & NOT DrvHasDisk(DRec.DrvPtr) do
					DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink);
				if DRec.DrvPtr = NIL then begin
					DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead);
					while (DRec.DrvPtr <> NIL)
					      & NOT DrvHasDisk(DRec.DrvPtr) do
						DRec.DrvPtr :=
						     DrvElPtr(DRec.DrvPtr^.qLink);
					end;
				if DRec.DrvPtr = NIL then begin {all empty}
					DRec.List := NIL;
					GetDItem(SFFileDlg,getEject,itemType,
						 itemHdl,itemRect);
					HiLiteControl(ControlHandle(itemHdl),255);
					GetDItem(SFFileDlg,getDrive,itemType,
						 itemHdl,itemRect);
					HiLiteControl(ControlHandle(itemHdl),255);
					end
				else begin
					Drec.List :=
					    GetFList(DRec.DrvPtr^.dQDrive,
						     numTypes,typeList,fileFilter);
					SortFList(Drec.List);
					{ if fewer than two drives have disks,
					  then deactivate the Drive button }
					tmp := 1;
					tmpDrvPtr := DrvElPtr(DRec.DrvPtr^.qLink);
					while tmpDrvPtr <> NIL do begin
						if DrvHasDisk(tmpDrvPtr) then
							tmp := tmp + 1;
						tmpDrvPtr :=
						       DrvElPtr(tmpDrvPtr^.qLink);
						end;
					if tmp < 2 then begin
						GetDItem(SFFileDlg,getDrive,
							 itemType,itemHdl,
							 itemRect);
						HiLiteControl(ControlHandle(
								itemHdl),255);
						end;
					end;
				DlogSetUp(SFFileDlg);
				InvalRect(SFFileDlg^.portRect);
				end;
			end;

	getDrive:       begin
			DelFList(DRec.List);
			DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink);
			while (DRec.DrvPtr <> NIL) & NOT DrvHasDisk(DRec.DrvPtr) do
				DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink);
			if DRec.DrvPtr = NIL then begin
				DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead);
				while NOT DrvHasDisk(DRec.DrvPtr) do
					DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink);
				end;
			Drec.List := GetFList(DRec.DrvPtr^.dQDrive,numTypes,
					      typeList,fileFilter);
			SortFList(Drec.list);
			DlogSetUp(SFFileDlg);
			InvalRect(SFFileDlg^.portRect);
			end;
	shiftList:      begin
			if multichecked then
				MultClick(SFFileDlg, DRec.VBar, Drec.rows)
			else begin
				GetDItem(SFFileDlg,getMChk,ItemType,ItemHdl,
					 ItemRect);
				SetCtlValue(ControlHandle(ItemHdl),1);
				MultClick(SFFileDlg, DRec.VBar, Drec.rows);
				SetCtlValue(ControlHandle(ItemHdl),0);
				end;
			end;
	getNmList:      begin
			if multichecked then
				MultClick(SFFileDlg, DRec.VBar, Drec.rows)
			else begin
				{ is this a double click?  Note that I do not
				  quite do this by the book; there are extra
				  delays in the times involved, since I don't
				  use the times stored in event records.  It
				  should all average out, though. }
				if TickCount - lastClick < GetDblTime then begin
					{ time OK for double click, check motion }
					GetMouse(tmpPoint);
					if (abs(tmpPoint.h - lastPoint.h) < 3)
					   & (abs(tmpPoint.v - lastPoint.v) < 3)
					then itemHit := getOpen
					else begin
						lastpoint := tmppoint;
						SingClick(SFFileDlg, DRec.VBar,
							  Drec.rows);
						lastclick := TickCount;
						end
					end
				else begin
					GetMouse(lastPoint);
					SingClick(SFFileDlg, DRec.VBar, Drec.rows);
					lastclick := TickCount;
					end;
				end;
			end;
	getScroll:      VScroller(SFFileDlg);
	getHScroll:     HScroller(SFFileDlg);
	getMChk:        begin
			GetDItem(SFFileDlg,getMChk,ItemType,ItemHdl,ItemRect);
			if multichecked then SetCtlValue(ControlHandle(ItemHdl),0)
			else SetCtlValue(ControlHandle(ItemHdl),1);
			multichecked := NOT multichecked;
			end;
    End;
  Until ItemHit in [getOpen,getCancel];

  { set last volume seen by standard file package }
  if DRec.DrvPtr <> NIL then begin
	  v := VCBPtr(GetVCBQHdr^.qHead);
	  while (v <> NIL) & (v^.vcbDrvNum <> DRec.DrvPtr^.dQDrive) do
		v := VCBPtr(v^.qLink);
	  if v <> NIL then begin
		ip := IntPtr(SFSaveDisk);
		ip^ := -v^.vcbVRefNum;
		end;
	  end;

  if DRec.List <> NIL then begin
	CompFList(DRec.List);
	if itemHit <> getOpen then begin
		DelFList(DRec.List);
		DRec.List := NIL;
		end;
	end;
  DisposeControl(DRec.VBar);
  DisposeControl(DRec.HBar);
  DisposDialog(SFFileDlg);
  SetPort(savePort);
  SFMGetFile := DRec.List;
End;

END.

!E!O!F!
#
#
echo extracting sfmget/sfmgetr.text...
cat >sfmget/sfmgetr.text <<'!E!O!F!'
* resource compiler input file for SFMGetFile resources
*

sfmget-sfmgetr.rsrc

* this is the dialog referenced as SFDialog in program
Type DLOG
  ,-4001
  0 0 152 358
  InVisible 1 NoGoAway 0
  -4001

* Item list for the SFdialog
Type DITL
  ,-4001
  12
  BtnItem  Enabled
  28 162 46 242
  Open

  BtnItem Disabled
  59 1552 77 1232
  Invisible

  BtnItem  Enabled
  90 162 108 242
  Cancel

*the volume name
  UserItem Disabled
  22 258 54 354

  BtnItem Enabled
  59 266 77 346
  Eject

  BtnItem Enabled
  90 266 108 346
  Drive

*the window in the dialog
  UserItem Enabled
  11 12 125 135

*the scroll bar for the window
  UserItem Enabled
  11 134 125 150

*the gray line
  UserItem Disabled
  20 254 116 255

  StatText Disabled
  20 1044 116 1145
Invisible Text

*the horizontal scroll bar
  UserItem Enabled
  124 12 140 135

  ChkItem Enabled
  125 172 140 346
Multiple File Selection

!E!O!F!
#
#
echo extracting sfmget/sfmtest.text...
cat >sfmget/sfmtest.text <<'!E!O!F!'
{$X-}
{$U-}
{$R-}

Program SFMTest;

USES
    {$U Obj-Memtypes  } MemTypes,
    {$U Obj-QuickDraw } QuickDraw,
    {$U Obj-OSIntf    } OSIntf,
    {$U Obj-ToolIntf  } ToolIntf,
    {$U Obj-PackIntf  } PackIntf,
    {$U Obj-WritelnWindow } WritelnWindow,
    {$U sfmget-SFMIntf } SFMIntf;

CONST
    AppleMenu = 256;
    FileMenu  = 257;
    FilterDlog = 256;
    TypeDlog = 257;

TYPE myTypeList = ARRAY [0..7] OF OSType;

VAR Finished:	   Boolean;    {used to terminate the program}
    myFilter:	   ProcPtr;
    bounds:	   Rect;
    numTypes:	   INTEGER;
    typeList:	   myTypeList;

FUNCTION theFilter(PB:ParmBlkPtr):Boolean;
VAR     theDialog: DialogPtr;
	itemHit: INTEGER;
BEGIN
	ParamText(PB^.ioNamePtr^,'','','');
	theDialog := GetNewDialog(FilterDlog,NIL,POINTER(-1));
	ModalDialog(NIL,itemHit);
	DisposDialog(theDialog);
	if itemHit = 1 then theFilter := FALSE else theFilter := TRUE;
END;

PROCEDURE SetTypes;
VAR     theDialog: DialogPtr;
	itemHit: INTEGER;
	ItemType: INTEGER;
	ItemHdl: Handle;
	ItemBox: Rect;
	s: STR255;
	i: INTEGER;
	tmp: LongInt;
	tp: OSType;
BEGIN
	theDialog := GetNewDialog(TypeDlog,NIL,POINTER(-1));
	ModalDialog(NIL,ItemHit);
	if ItemHit = 2 then begin
		DisposDialog(theDialog);
		exit(SetTypes);
		end;
	GetDItem(theDialog,3,ItemType,ItemHdl,ItemBox);
	GetIText(ItemHdl,s);
	StringToNum(s,tmp);
	numTypes := tmp;
	for i := 0 to 7 do begin
		GetDItem(theDialog,4+i,ItemType,ItemHdl,ItemBox);
		GetIText(ItemHdl,s);
		BlockMove(POINTER(ORD4(@s)+1),@tp,4);
		typeList[i] := tp;
		end;
	DisposDialog(theDialog);
END;

PROCEDURE Break; inline $FACE;

PROCEDURE ProcessMenu_in(CodeWord:longint);
Var
  Menu_No:    integer;	      {menu number that was selected}
  Item_No:    integer;	      {item in menu that was selected}
  NameHolder: Str255;	      {name holder for desk accessory or font}
  DNA:	      integer;	      {OpenDA will never return 0, so don't care}
  pt:	      Point;
  h:	      StrLHandle;
  err:	      OSErr;
  i:	      Integer;
  reply:      SFReply;
Begin
  If CodeWord <> 0 then
  begin
    Menu_No := HiWord(CodeWord);   {get the Hi word of...}
    Item_no := LoWord(CodeWord);   {get the Lo word of...}

    Case Menu_No of
      AppleMenu:Begin
		  GetItem(GetMHandle(AppleMenu), Item_No, NameHolder);
		  DNA := OpenDeskAcc(NameHolder);
		End;
      FileMenu: case Item_No of
		1:      begin
			pt.v := 100; pt.h := 100;
			h := XMGetFile(pt,myFilter,numTypes,@typeList,NIL,
				       BitOr(SFMmulti,SFMenable));
			if h <> NIL then begin
				WriteLn('Volume ',h^^.volume:1,': ',h^^.vname,
					' (',h^^.howmany:1,' files selected)');
				for i := 0 to h^^.howmany-1 do
					WriteLn('   ',h^^.table[i].str^^);
				DelFList(h);
				end;
			SFMEnd;
			end;
		2:      err := Eject(NIL,1);
		3:      err := Eject(NIL,2);
		4:      if myFilter = NIL then begin
				CheckItem(GetMenu(Menu_No), Item_No, TRUE);
				myFilter := @theFilter;
				end
			else begin
				CheckItem(GetMenu(Menu_No), Item_No, FALSE);
				myFilter := NIL;
				end;
		5:      SetTypes;
		6:      begin
			pt.v := 100; pt.h := 100;
			SFPutFile(pt,'Having fun with your','Luau Frog Giblets?',
				  NIL,reply);
			end;
		7:      Finished := True;
		end;
    End;{case of Menu_No}
  end; {the If codeword <> 0}
  HiliteMenu(0);	       {unhilite after processing menu}
End; {of ProcessMenu_in procedure}

PROCEDURE DealwthMouseDowns(Event:EventRecord);
Var WindowPointedTo: WindowPtr;
    MouseLoc:Point;
    WindoLoc:integer;
Begin
  MouseLoc := Event.Where;
  WindoLoc := FindWindow(MouseLoc, WindowPointedTo);
  Case WindoLoc of
     inMenuBar: ProcessMenu_in(MenuSelect(MouseLoc));
     inSysWindow: SystemClick(Event,WindowPointedTo);
     inContent,inGrow: WWMouseDown(WindoLoc, Event.where, Event.modifiers);
  End{ of case};
End;

PROCEDURE DealwthKeyDowns(Event:EventRecord);
Var CharCode:char;
Begin
   CharCode:= chr(Event.message MOD 128);
   If BitAnd(Event.modifier,CmdKey) = CmdKey
   then ProcessMenu_in(MenuKey(CharCode));
End;

PROCEDURE MainEventLoop;
Var Event:EventRecord;
    ProcessIt: Boolean;
    DIPt: Point;
    Fake: OSErr;
Begin
  Repeat
    SystemTask;		    {so you can support Desk Accessories}

    ProcessIt := GetNextEvent(EveryEvent,Event);
    If ProcessIt{is true} then {we'll ProcessIt}
	  Case Event.what of
	    activateEvt: WWActivateEvent(Event.modifiers);
	    updateEvt  : WWUpdateEvent;
	    mouseDown  : DealwthMouseDowns(Event);
	    KeyDown    : DealwthKeyDowns  (Event);
	    diskEvt    : if HiWord(Event.message) <> noErr then begin
				DIPt.v := 120; DIPt.h := 150;
				Fake := DIBadMount(DIpt, Event.message);
				end;
	  End;{of Case}
  Until Finished; {terminate the program}
End;

BEGIN
  InitGraf(@thePort);
  MaxApplZone;
  MoreMasters; MoreMasters; MoreMasters; MoreMasters;

{init everything in case the app is the Startup App}
  InitFonts;
  InitWindows;
  InitMenus;
  TEInit;
  InitDialogs(Nil);
  InitCursor;
  WWInit(200,80);
  bounds.top := 40;
  bounds.left := 10;
  bounds.bottom := 340;
  bounds.right := 500;
  WWNew(bounds,'Test Window',FALSE,TRUE,1,9);
  Finished := False;
  myFilter := NIL;
  numTypes := -1;
  FlushEvents(everyEvent,0);
  AddResMenu(GetMenu(AppleMenu),'DRVR');
  InsertMenu(GetMenu(AppleMenu),0);
  InsertMenu(GetMenu(FileMenu),0);
  DrawMenuBar;		 {all done so show the menu bar}
  MainEventLoop;
END.
!E!O!F!
#
#
echo extracting sfmget/sfmtesto.text...
cat >sfmget/sfmtesto.text <<'!E!O!F!'
sfmget-sfmtesto.rsrc

Type CODE
  sfmget-sfmtestL,0

Type SOFT = DRVR
  sfmget-sfmgetfileL!SFMGetFile,1 (16)
!E!O!F!
#
#
echo extracting sfmget/sfmtestr.text...
cat >sfmget/sfmtestr.text <<'!E!O!F!'
*  Input Resource File for Standard File example
*

sfmget-sfmtestr.Rsrc

* apple menu, 14 is apple symbol in HEX
Type MENU
  ,256
  \14

* file menu
  ,257
  File
  Open /O
  Eject Internal /I
  Eject External /E
  Filter Files /F
  Set Types /S
  Put File Test /P
  Quit /Q

Type DLOG
  ,256
  80 80 170 380
  Visible 2 NoGoAway 0
  256

Type DITL
  ,256
  3
  BtnItem Enabled
  55 20 80 130
YES

  BtnItem Enabled
  55 170 80 280
NO

  StatText Disabled
  15 20 45 280
Should I allow the file "^0" to be used?

Type DLOG
,257
178 163 328 463
Visible 3 NoGoAway 0
257

Type DITL
,257
12
BtnItem Enabled
105 40 137 120
OK

BtnItem Enabled
105 185 137 265
CANCEL

EditText Disabled
15 205 32 245
-1

EditText Disabled
40 25 56 75
TEXT

EditText Disabled
40 95 56 145
MACA

EditText Disabled
40 160 56 210
APPL

EditText Disabled
40 225 56 275
MACS

EditText Disabled
65 25 81 75
ZSYS

EditText Disabled
65 95 81 145
PFIL

EditText Disabled
65 160 81 210
FNDR

EditText Disabled
65 225 81 275
DAMN

StatText Disabled
15 25 31 185
Number of File Types:

!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting cust.Hqx...
cat >cust.Hqx <<'!E!O!F!'
(This file must be converted with BinHex 4.0)

:"'0eFh3!39"36%098e3J!*!('J#q0J#3"!%!N!-B!*!$&`#3"1)!N2-L)8098e3
J9Q9bFfP[EL!b,#!a-#"6CA"dC@eLCA)J-6Ni03#3!`G"8&"-!*!'(%098e3!N!-
"5801)`#3"B"'8N9'!*!&J!!!!H!!$3#3"43!EJ!M!,i%!Np,!*!&&!%%!#-"9!3
'3f&ZBf9X!*!&-J$$!%%"AT!!&84PCQ&eE(4-Ef0KE%P33@4NFQ9cF`#3"NX!``"
D!9k3!"*%C@CKG@ad4eG*8%&NC(*PFh-!N!9N!--!F`&HN!!54'9QBA9XG%j659"
"C'4bCA0c!*!&I3$$!)`"AT!!$d4PCQ&eE(49Ff9b6Q&YC3#3"TB!``#P!9k3!!p
%C@CKG@ad5'pcG%jKE@8!N!D[!!S![J&H"5e#BA0P)%a[Bf&X)%P3)%&NC(*PFh-
JEfiJ3A"`E'9LGA-J6QpNC5"1G@eLCA)!N!Bb!!S!33#qL"&-Ef0KE#"*8#""C'4
bCA0c1J#3"NX!#J"D!,k)%dGKG'9hBANJ59!J3@4NFQ9cFcS!N!CN!!S!F`#qL"G
1B@eP)&0PFRCPFL"*8#""C'4bCA0c1J#3"Rd!#J#-!,k)#P9cCA)J6Q&YC6S!N!@
@!!S!T3#qL"9%C@CKG@ad)%C[FQ9TCfiJ5'pcG$S!N!E)!!S""!'3!)JX3h9cG'p
YDATKG'P[EL"QD@aP)'j[G#"QEh9ZC#!Y,5"MFQ9KG'PZCb"TG#i!N!-9!"i!&!%
X!HS!!3%!N!J$!*!%0!%!N!RrN!3%4QPXC3P$GA0dEfeTHQ8!N!3%9A0PFJ#3"!4
4G@Pd!*!(!3#3"32Mjr!%!J#!"!)!J!3#!)!$`i#!!#)!J!!L!)!!)J#!"m2JJ!#
3#83F!!"%)J!!4#)!!%3L!!"%2!!!4#!!!%3J!!"%)!!!1#!!N!Tm(!!!%#)!!"!
L!!!3)J!!%$`!!"!J!!!3)!!!%#!!!(`J!!rrrrJ2rrri$rrrq!rrrrJ2rrri$rr
rq!rrrrJ2rrri$rrrq!rrrrJ2rrri!2rr!!$rr`!!rrm!!2rr!!$rr`!!rrm!!2r
r!!$rr`!!rrm!!2rr!!$rr`!!rrm!!2rr!!$rr`!!rrm!!2rr!!$rr`!!rrm!!2r
r!!$rr`!!rrm!N!3`!*!$3!!!!J#3"#!!N!-J#1ir2!!"UI!"2Mmm!!1Tm!!!2c`
!!kR`!Nir2!!$UI!!!!k)!*!$!8j@r[`[,Ir@2bi!#%KYrbT)E[rm5'hr)UQ0,bi
!#NKZr[a1ZJbF,blrr%KZr[bTMdjH)&pF6dl36PEqr#mYrpBr,J!)5'hr+NKZrra
)EImLUBd[,[rm5'lqr+Q3!%+R5'lqr%kk$53YA`!+6PiJAe426Y"19[R'51F2!%*
R5'lkb%KZqXC1ZJZH5PpR"%*ZqXJ`,[V)5-$3[)!#!!!Y32hF,Ab!!J"!rH!YI)!
#!%$pj#emJ!)!32hS3Hlpl%2k##BJf5$C-*&"l[lZ3rS)%#$C)0NGI!!"rZa#CdK
k"l"#CdKZrqj1ZJKk2Kp+4fC),A`!!!)5rGK#CcmZrqir2!!"3UG1ZJPU2Kp+4fB
@3QFr,[rZ5'lpf%(ZrG`[#%kk#*Jq(d(Zr0C$qJH#F!`Jf90!E[S`N@"H$%Ire@C
B3QG)HJG-3QF[1JGJ,cS(@%kk#,3q(dT(CK*#CdKk"c*#CdKZrqj1ZJIm2KmYI!!
!!K,pf%T(CK"#CcmZrqj)E[hB6VS)aMiI3HlmeN2k"X*`%#$C8d"ZqNT(Cc")abm
(5'lmeNkk#24)HJCk5'lmeNKZqFBr2!!#6VS*'N(Zr0C$l[R'F%!Jf90!E[T#Tcm
m!!&"lImX,`K`rbm!UA`VArr@,bhreMmm!!K)EImU5'lrp%KYrb+TM5mYrpBr2!!
15'hr+NKZrr")EImLUBd[,[rd%#lql%L!2`#TBbmZrG`r2!!$6VVphLmZrH!r2!!
%6VVpdLmZrH3r2!!&6VVpaLmYrpBr2!!'5'hr+NKZrrK)EImLUBd[,[ri5'lpl+Q
2,bhreMmm!!G)EImU5'lrq%KYrb+TM5mZrrK)E[lZUBm[,[r`5'lmeUQ23UG)E[r
mUC&#,[c@3UFr2!!$6VVpRL`I5SCQ%N(Zr0C$qJ9BF!JJf90!E[TJ"#e'rGa)E[c
@5(S%N!"1ZJKk%"r!,[lXCbC#TbmZrG`[22q3!`#S@$!ZqXK)`0#I,8$ph#mZrG`
r2!!$6VVp%%+R2c`!"%kkr6iU(dU&CK4"l[c@3rS%eR!))0P63'lk-*&J"#e&rH"
#Tcmm!!91Z[d@+"p+K'B83HlmeN2k")K`#5$C8d"ZqM#4B!3Y42hN,bhreMmm!!C
)EImU5'lrq%KYrb+TM5mZrrK)E[V+UC!!%#lkbJ*!!2m-3!!)Ea*"l[c@3rS%(R!
*)0P63'lkB$!3,[V+!N!!rdT!CK4"l[c@3rS$hR!))0P63'lk-*&J%%(ZrHa$l[V
+F%!Jf90!E[S[,Ir@2c`!"dKYrbT)E[ri5'hr)UQ0,blrq%KZqmUTN!!3,[[+!N!
!rdT!CK4"l[c@3rS$E(!))0P63'lk-*&J%%(ZrZj$l[[+F%!Jf90!E[S`,[rm8d"
R%&0!C`!"%Pe!C`!"B'!!!Ea)E[c@5(S$,Nkk"aJ3(fF!!1a"l[c@3rS#r(!))0P
63'lk,blrm%KZr0DTMd+R5'lrr+Q4$'i!!IrmCb3pI!!1rra"l[c@3rS#Y(!')0P
63'lk,blrm%KZr0DTMf!!!9`YI!!!!K,pf%*R2blrlMmm!!&#Tdkk"Giq(dT(CKC
#CcmZrqj)E[hB3Hlph#m)6VS&%$iI5NGR)N(Zr0C$qJ)fF!SJf90!E[S[,[r`5'l
meUQ23UG)E[rmUC&+4fB-3QFr,[rZ6VS%ZMiI5NGR)N(Zr0C$qJ(BF!SJf90!E[S
[,[r`5'lmeUQ23UG)E[rmUC&+4fF'2A`!$[rmB!BpI!!1rraJ!!#f3HlmeN2k!A*
`#b$C8d"ZqM#4,blrm%KZr0DTMd+R5'lrr+Q4$'i!!IrmCaSpI!!1rra"l[c@3rS
",R!%)0P63'lk-*&JEN*R2blrlNkk"#`q(f"J%#lql!S!!!%G32lX,blrp"!ZrZa
)J$m!U@-3,[lXCbj"l[c@3rS!XR!1)0P63'lk-*&#TbmZrG`[22q3!`#S@$!ZqXK
)`0#I,8$ph'!53HlmeN2k!$j`%5$C8d"ZqM#4-#lrr'XBX(`!"fi53IS!*$)!jNP
%33%`%2m+2!!%CJ$m&LmYrpDTJNcI!2"1ANje!!C&6'pMB@`J59!J3@4NFQ9cFb"
TFb"ZEb"XEfjRCA)JB5"QG@jMG'P[EL"[CL"dD'8J3A"`E'9LGA-JEQpNC5"ZG@e
LCA)Z1%a[Bf&X)%P3)%&NC(*PFh-JEQph)'%JCR9ZBh4TEfiJEfBJ3A"`E'9LGA-
JEQpNC5"ZG@eLCA)Z!"&$B@jMC@`J*f0KEQ0PE'9N*ba"FQ8JH@pe)(0eFQ8JH@p
e)(GKER3JG'mJBf&ZBf9X2b!S8f9XC@0d)%p,+3!R9(*[G@*XC5"ME'pcD@jR)(4
SC5"MGA0dEfeTHQ&dD@pZ)'CTE'8Z*e4bEh9LE'8JGh*TG'PZCb"dD'8JBh9cG'p
YDATKG'P[EL"QD@aP,KG6BACP)'KKFb"LC@9Z)'0KEQ0PE'9N,Kp3E'9KFf8JBfp
ZCQPbE5"LH5"ME'PMDfPZCb!R6dXR!!!J6R9XE#"SEh0d)'jKE@8JDA-JEQpd)("
PFQeTG(4PC#i!)%jeE'`JGA0PFL"ZB@eP)'Pc)'j[G#"`CA*YDA4dC@3Z!#*9Ff9
b)'jKE@8JCh*PBA4PFL"dD'&Z)$JJBfKKFQ&MG'9b!#91B@eP)&0PFRCPFL"*8#"
"C'4bCA0c)'Pc)'PXE#"QEh*YC@3Z)8GKG'9hBANJ59!J3@4NFQ9cFb"TFb"TE'`
JCQpbE@9N,Kp-Ef0KE#"*8#""C'4bCA0c)'Pc)'PXE#"QEh*YC@3Z+d0[G@aN)'j
[G#"[F'9Z)'0eFh4[E@PkBA4TEfiJCQPXC5!Y)(0dBA4eFb!r3h9cG'pYDATKG'P
[EL"QD@aP)'j[G#"QEh9ZC#!Y)'0bC@&dD@jR)'Pd)(GTG'JJC'9QBA9XG#"fB@a
eCA-Z&%0eFh4[E@PkBA4TEfiJ9Q&XG@9c!&4&@&3rN!3a3h9bFQ9ZG#"fB@aeCA-
JCR*[E5"MGA0dEfeTHQ&dD@pZ)'CTE'8JBA*P)(0SEhGZ,JG9EQYZEhGZ#%&`F'a
P68&$!%kk!EK19J!!,&p193!!Rqd!%%kk!ET"lIlq,`LSEUMq2ccrrd*R)"qJ-UN
5UFa#TkPlU&""lIr`3qhqLL$C)0P)EIri2c`!"$mm!"J`,Irf@8!r!$!Yrr4C3$m
!U+G1Z[FN6VS"GNjG6VS"BNje6Pj1G8j@rmj"l[r1)@i!$J!5-@i!$!!@3LJ!'N)
S!"Y#U!!FS!!LEJ!)-UJ!'$e!!"*1AL"Ihr`!N!-+6Y"19[r13HlrcM&Z!!J!'+!
"28!!#NjH)&p8Mdl38F&J!P$"6PErcN(ZrmiKEJ!)!#!aEJ!3!"JLEJ!-)9%!*%*
S!#a#U!!Z5J&Q"+!#B!+J!ce!!")LEJ!-)UJ!+%jH)PrIr!#3!`T1d8j@rl""l[q
`)@i!%J!5-@i!%!!@3LJ!'U!)5N"Q&N*S!"bJ$#!)3qJ!)#,Z!!JLVJ!-S!dp3!!
@6PiLAprm!*!$$Nl46PErcN(ZrmiLEJ!))9%!*$&Z!!`!'+!328!!$L+S!#K1AL*
IA)p1d8j@rmj"l[r1-@i!$J!B-@i!$!!X)@i!#!!ZS%3p3!!36PiLAe#26Y%JE`!
%)#m!#%*RUHiJAe"26Y"#V`!33IS!###[!!41G3#3"%je)&p1A5m)6VS!VNje)&p
193!!6Y"19J!!,&p)jq$J-#m!(#"[!"j$l`!L0!$P5Y,#3N&5L'!3*'&#3K3DdN*
J!K$D8FVrr&()rqiJE`!H%)&$l`!L-#m!(19)dX!M,`!B,dN!'%cI"`FZAdje6Pj
1G59I3d&8)*!$51I!`()"B!C)jm$!3N%JE`!B)Qm!&%*!%"L`'@B1B!5c#'B)8d"
Uq!T"!!%I33!D,fm!%!!@60m$!eb26R919J!!3Uhr(N)YraK#,ImC3Uhr%N+Yr`j
#VIm#6Pj1GD9*6NP85%9"!!"`!'!!!)T`!@!!!)4`!Q"qF!0JHR!%B(C`"@"bF!C
JER!(B'T`#'"QF!PJBR!+B&j`#f"DF!aJ9R!0B&*`$Q"1F!pJ5R!3B%C`%@"#F"*
J2R!6B$T`&'!fF"9J-R!@B#j`&f!UF"KJ*R!CB#*`'Q!HF"YJ'R!FB"C`(@!5F"j
J$R!IB!T`)'!'F#&J!R!L(`!`H!$JX2J#VQ3!!!iJH!$J)&!J8%U3!'BN98m[2'&
dF'bTR$!ICb*C6bmmBA4`E$m!UCdJ(fF-)%!I2!!M,a"#&dje-$J+B'B%-$cr3(3
!FJ!8(b*I3IS!'K)`)!$H`3a#!#*R#Ja#!!&Z!N*!2S"1d3#3"!B#"JB%#!))#!3
!!!J#"T!&"!D3"!i'"!!!#!!!6PEkr#eZ!!crr%+R%#lrr!*!!2p)`#m!,c`!N!2
rU&K)E[[m6VVpX%+R%#lrr3*!!2p)`#m!,c`!N!2rU&K)E[cm6VVpNN+R%#lrrJ*
!!2p)`#m!,c`!N!2rU&K)E[hm6VVpG%+R%#lrr`*!!2p)`#m!,c`!N!2rU&K)E[l
m6VVp9L"Z!!J[#%KZqra)HJ!i5'lmr%Kk!$")E[hm5(S!+%KZr[a)E[Vm2c`!"dk
kr@)JAd2Zq[a`3#$C8d"ZqNjH)&p36dl3!5j19[rf51F2!#"Z!!J3%!*!!2mq!%T
(CJK#VJ!-B!!!a#"Z!!K`!4)`!!!#33$rDaLbI!!rEK*"qJ$%-!(Q5%4!!c!!r`S
m!!4Q@%)%$%F!#'`%1JGJ!RS)28ArpR`#B"JJEJ!)%$"J!!*!!2m-3!!ZCJ*i!9*
'D3DmE[rfEq)3"'F33UF[,J!)6Ud!1LeI!!aJ8%+R,bi!#%kY!#SYA`!-B%!JEJ!
)F!%5-!!!!N%!r`a"!#0A`#"Z!!Kb!43`%!!#3J$r$%)!*&I"J!&R%%+R,bi!#%k
Y!$)YA`!-B!4#VJ!-3Hd!-Lm)UI&-h`$`6PiZRdje!rm!N!N%!!J!N!3$C!!)!!0
19[ri51F$##KZ!!J3&!*!!2pV',"m!!pZ%N(k!5)b!1C*4%%"-"$r#M`!"'F)3Ui
!$'!!!1K#Ka!8!N!!rce!rrKm!Q!!!-S30'!!!N!!rfXBX(`!2fi53IS!iM)!jNP
%33%`%2m+2!!%CKSJ"qQ!%M4J!!*"!2q5I!!`5-(5J#i"B!!!L"!dB!!#3!$rDaL
`I!"[EK*"qJ#F-J$Q584"!6!3r`Sm!!4Q(#!(kB!50'!!!N%!rj*m!'(5I!!+5-(
5J#i"B%B30'!!!N!!rfXBX(`!6fi53IS!@$)!jNP%33%`%2m+2!!%CK`J"qQ!%M4
J!!*"!2q5I!""dR`!#NM"dS!Z!@!')!ITJ#i!8NCT#,aZrrK[!2mb,8F!$%cI%-"
1ALkI6R8!IJ#3$!2r!*!'!ra19[rZ51F2##KZ!!K`!4fmri!!mA!#(E`!!J$aF!0
#0J$aF!4#0J$aIJ&m!@!!!)C#44!8!N!!rl"(A-!50(!!!N%!rfXBXR`!2fi53IS
!`M3"jNT%3J-`)2m+2!!%9m(!!@FN)!AR3")dF!!#33$rdN#5I!!`1J%-43$rE`C
#VJ!-B(T54f#U)!BGK3$l1!B3&!*!!2q`4fdL%$4`!!*!!2m-3!!XCJ454f!'3Ui
!$'"-8NB-4J!%E`$rGK!8!N!!rl"(E3C#VJ!-B$)pI!!%rrTJ($!ZrrS50N$l!N%
!raf"!2&64!a%!!&Y$&0ZrrS-EJ!"rrTXh#eZrr)!$%cI%2"1ALkI6R8!r`#3"Nj
@rqj)j`m)+'i!#(!"(EcrJ!$aF!)G[!!#!2&`!d)f!2&`"%)f!2&q!A`"B!!!L%*
&%"3#3!$rX%GF`")dF!!#33$rDaLbI!!rEK*"qJ$%0!(Q5N4#!c!Jr`Sm!!4A`F!
"CbBJ"F(m!!S50(!!!N%!rp*!NR`!-$S"$%8!rfm'3Ui!$'"k8NGJU#!'(B8!qcJ
'%"3#3!$rX%GY)K!dF!!#3!$r$%!!,QB%8NGJ"N+Z!!aJ6&*'$%B!"'m!rh33&!*
!!2q`4fd'3Ui!$'!b2A`!"2rkB"``,[rk%MC!q`*"!2mGJ3$a8d3-4!!"E3a6E[r
k$'i!!IrkE0`YE[rb!!a-ha$`6PiZRdje!rm!N2m!N'BF!1)!"d098e3!N!0#4P*
&4J#3!dj#6N4-!*!$@N4*9%`!N!0Q4%a24`#3!h*048j9!*!$INP$6L-!N!1+3dp
%43!$!*B!!2rr!*!*J2rr!*!$*J#3"B$rr`#3!c%!N!8$rrm!N!04!*!&!Irr!!!
#03#3"!%!rrm!!!*1!*!&J2rr!!!#KJ#3"[rr)!!$LJ#3"3(rrc3!!li!N!8#rrm
`!"*+!*!&!rrr-!!58J#3r`#3)j%V!:
!E!O!F!
#
echo extracting sfmtest.Hqx...
cat >sfmtest.Hqx <<'!E!O!F!'
(This file must be converted with BinHex 4.0)

:"h0QEA4PFh3!39"36$q3"#!!N!G#lF01!*!%!3#3!d)!N!0"!*!%l3#3meS!!J#
3"6F!&!"3!))%!eP&8`#3"MF!UJ"3!4J%!Nj2!*!&$`!8!#d"')JS8fK[G@aN)%N
JB@aXEhFJG'KP)'CTE'8J)Pi`)L"dEb"LC5"eFf9N2`#3!qS!#`#3"@N!+!#*!(J
%!Np,!*!&D3#j!)N"#33'3d&13d9-!*!&$`$0!#!!pC!!!Lda!*!&+!!C!$J!5j!
!"&4&@&3!N!8S!&m!1!#4N!!%68&$33#3"5J!S!!i!0+3!!4"8&"-!*!&+!$K!$J
"%j!!"%e"3e-!N!9"!"N!83",N!!%@P0C8`#3"8%!A`"4!*'3!!434NP-!*!&33#
J!&%!dT!!"%C14&)!N!9"!1%!83%6N!!%4%&06J#3"3m!'3!I!,Q)&8jeE@*PFL"
[CL"'D@aP)&4jF'9c1J#3""8!8!"3!+S"I!!#!3#3"`%!N!89!,)!S`&)!Fm!!`%
!N!F"!3#3""%"!*!*rj!%!43!N!5%!3%!N!MrN!3%4QPXC392F'9Z)!"2!!!24@T
PBh3J5@jdCA*ZB@`J!%N!!!p&DQ9MG#"&H(4PFQjKE#!!43!!$8CTE(4PFL"'D@a
PFb!!4J!!#P0PG#"8HA"PFb!!8`!!$P"eG#"'D@aP)&4PFh3J!&!!!!94G@Pd)!"
4!*!'qJ!,!*!&(!#L!#i!mJ3')#"2F'9Z!*!&1`B3!%d%d)3,)#"*ERCTFfPLE'8
!N!CD!+)!E!$b"!JJ)%0KEQ0PE!#3"4B"!J!f!@+!!*!'1`%+!%d"@J3()#"&DQ9
MG!#3"PS"#J"X!9S%"b!J4(*TGQ8!N!B,!!`!I3#(!*!(#`#'!(d!PJ#3"a3!rJ"
d!2q!!*!'&!38!(3%HBJ15@jfDA0TBQaP)&4PH(3!N!9m!!`!M!#(!*!(I3#X!)`
"@J8A6A9XG'P`E'8J4QPXC5"6C@aPBh4TEfi!N!39!*!&Q!&Q!!%!N!M`A`#3!b+
f6[S2&%lk%AK1qKK`6[S4l%lk%a!JE`!%,Tp1d%j@rmC)j`mB*Qi!#%+R,`ZT&b"
I,9$rj%+R,`ZT&b"I+#J!"#m,2c`!"dKZrpa)E[rB5'lrd+Q03Hlrb%2Zrp!Jf5$
C5'lrb$mm!!%r2!!"U+P)E[r)U+0#Tbm,U4FJAbKS!!JJ$'B%B!!!d%KZrrLSLc!
Zrp$3E[ri8N!q!%*R,blrj+PJ1Kp#Tbm,U4FJAceS!"MraN*'B!!!Q#!'d%8J9,"
3E34J!!#5-#lrdP4!,`"#Cbm%U@!b(b!Ijd'3!%%r!$m(U*-J9#!'d%A"r!!')(!
!)#m3U)3J9#!'d%A"r!!'%M!!*'Fb5'lrl$!Zrp*53$m!)!H3!'lrq$m!-#lreP0
!2`!`,[rkd%Fb,[rqdN!r!DLR5'lrl+LN-#lrq0"(-LlrqY*!-#lrrY""2J"54QN
)['lraQm!rf4-haM`6PiZRdje6PErlLmZ!!Sr,J!)5'lrrNKZrrT)E[rbUBdpE[r
brqipE[rdrr#SRMmZrr!r,[rZU*-`,[rZX'lrpQ`D3QFr2!!#U*3`,[rZ9%!p32r
Z3QG#CkL5B0a1AL"IA%p1d%j@rpT)j`%B,bi!#MmZ!!K)E[rq5'lrqNKZrr+TM8K
Zrr+SSd+R,bi!#UNA)&mSD!!))!aQ"'!!!**)E[rQU)Y#TkMB*Pm[#kKk5'lrmUK
l3QFJ9%KS!!5SM$iI-#lrq*!!E[rdX%G[1M!ZrrL3!'lrp*!!4dM!JI`!!Y"Zrr3
r!$!Zrr*@3$)ZrrD5E[rb5-'$r!!#dN!r!DL6)&4)D!!%U)4J)#!8@)"5J#m!)&3
3+!!%!N!!rdM!,`")E[rb2c`!!DR1,`ZSH5m,U0P-haL!6PiJAea26Y"19[r-51F
2'%+RU0JSAd+RU0JQAd+R,bi!#+NA,KmJ4bS3,bi!#$mm!!G)E[rF5'lrf%KZrp#
TM5"(1#J!&%*R,`@TB$`I5'lriUL,)!53!%Bb,[rNdQlriM3ZrqM83F(#28$rqNK
Zrr)r,[r52blrd$mZrpBr,[r8U+G)E[rb2c`!!6mm!!'SU8KZrr*#CcmZrrS[$+M
[,`ZSHL"85'J!!UKl,bi!#%kkr1![#kKj)%Fa4J!8,`ZSf5m-U0P-haM`6PiZRdj
e6PB!!#m-+'i!#JaZ!"8!#&I!CMB[!%*R,`bTB$)I)"p)jm!!3QF[$+PL0"p-h`!
$Y%&H`F!"Ca)[$%*R,`bTB$!I8N!r!+PMB$i-EJ!8!!KA`'Bd,`"#Cbm-U@!b(b!
I51I!!%*R,`bTB63I60m!!l4"AF(!!@F3,`a#Cbm-U@!`(e0!2`#TBb"8,bJ!"%k
krUSSAdjH)&pF6dl36PB!!#m-+'i!#JaZ!"B!#'BJ,`a#Cbm-U@"#Tb"8,bJ!"+N
A)&m`(j!!D!!B2`#TBf!Q$'i!&`!)CKi[$%*R,`bTB%+R)&3[+!!%U4FJAc!S!"M
3Acm!U@0#Cbm-U@"#Cbm-U@)`(l"IE!`[$%*R,`bTBUPMB"a#Cbm-U@"#Cbm-U@%
`(l"IE`S[$%*R,`bTBDPM)&3[+!!%6VVq"#KI6PiJAea26Y"19[rd51F$!%KZrrL
TFN*R,blrq#mZ!!K)E[rmU@`q(dT(Ch``"`4!!"4R&&0!CbC63'Fi8d"R3!4!!'T
R4Q"Q3QF[,[rm,blrq%(krR)[#+PS2"pJ8%*R,blrr#mZrrK"q[jF,`LTD$`IB$S
[,[rm2`G1Z[lQB#i[,[rm2`G1Z[lDB#*#CbmZrr`[,[ri3UHTD%TIC`J[,J!)6VV
pA'!'2c`!!kR)60m!`%jH,Tp1G8j@rp4)j`mB3UHSf#KI3UHSf#CI3UF[,J!)U4F
Z(b"(+LJ!"#mZ!!Jr2!!(5'lrj%KZrq")E[rBUBdJ4cJS!"C#Cbm&U@!m(b!%N!"
'jd!p32rk5'lrmMmZrpSr,[rB2blrhMmZrpbSTdKZrr)r2!!"2c`!!DLT5'lrmMm
ZrrT#Cbm-U1m[#kKk)&4)D!!#U(X[,J!)6VVk1Lm,U(NJ4c&'!"B[#kMC,`bSf8c
I'2"1ALkI6R919J!!,``SEJ!+$'i!&3!)9m"Q0Lm!3QF[$+PJ-KmJ(dMR`!"#Cbm
-U@)d(dcI!!1d39l"`!&R%Lm-3QF[$+PJ-"p53$m!U@0J2JaZ!"3!#&I!CM3[!%*
R,`bTB$)I)"p)jm!!3QF[$+PK0"p-h`!$Y%&G`F!"Ca![$%*R,`bTB$!I8d!r!+P
M)&3[+!!%6VVq[#KI6PiJAea26Y"19J!!,``SEJ!+$'i!&J!)CK)[$%*R,`bTB$!
I@d!r!+PMB"J-EJ!A!!KQ%#m-3QF[$+PJ-"pD3$m!U@0#Cbm-U@"#Cbm-U@)`(l"
IE!`[$%*R,`bTBUPMB"a#Cbm-U@"#Cbm-U@%`(l"IE`S[$%*R,`bTBDPM)&3[+!!
%6VVq-LKI6PiJAea26Y"19[rd51F$!%KZrrLTFN*R,blrq#mZ!!K)E[rmU@`q(dT
(CeS`"`4!!"4R&&0!Ca"63'FL8d"R(J4!!'TR*'"%3QF[,[rm,blrq%(krSi[#+P
S2"pJ,LmZrr`r"dkkraKJ)N*R,blrr#mZrrK#TkPS5PpR##mZ!!K1Z[fXB!Br2!!
$UFK-h`$!6PiZRdje6PErkLm-,bi!#MmZ!!K)E[rq5'lrqNKZrr+TM8+RU0JSAbm
-U(T)E[rbU(Y)E[rbU+%[,J!+6VVi-#m-U(N[$+MC+&p1AL"IA%p1d%j@rqK)j`-
)3UF[,J!)U4FJAbKS!!K#"L"8-""63$e!rqK#4f!8)&3J"m(m!!B5-!!NC`*m!9*
(D3DqE[rSEqB[,J!)2c`!!8KZrrC)E[rb5'lrkUQ0%!CR#LmZrr*#CkPGB!S[,[r
b2c`!rkPG60m3`%jH,Tp1G8j@rmT)j`m)3UF[,J!1U4FJAbKS!!JJ$'B+2c`!!kR
)B!!$1NKZrr+SLc!Zrr63E[rb-Llrq0*!1!%[,J!12c`!"dKZrq*)E[rH5'lreUQ
0HJ*#CbmZ!!UTBMeIrmj#CbmZ!!UTB$iI3QHTFa!IC`!#j+Qd5'lrr+Pb-#lreP*
!X'lrr'm!!*4+4fm1,bi!#L!(8d!r!+PMB!*Jc&0(5)8`"@Fm8d"R5&0!C`*J6L"
8)!I"r!!'%M!!*'F%HJ&J!N)&)&3J"m(m!!B5-!!N#J%!!5"8)!I"r!!'%B%!*'!
F)&3J"m(m!!B4[!!"!#4J$#"8)!I"r!!'3M!!*$e(rmS[,J!16VVj&R!%,`")E[r
56VSBbQ!!re*J%M!ZrpC53$)Zrrb53%M"Jm3m!6!Z!!K63,"'E!!![,jZrmjX$Lm
Z!!SJ"e*!2`#TBf!%B!$r'P*(5)8`"@G18d"RB&0!C`*JE#"8-#i!#&0!d%I"r!!
'%M!!*'F%HJ&J!N)&)&3`,J!)8d$34m(m!!B5-!!N#J%!!5"8-#i!#&0!d%I"r!!
'%B%!*'!S)&3`,J!)8d$34m(m!!B4[!!"!#4J%L"8-#i!#&0!d%I"r!!'3M!!*$!
Z!!K630"(28$rbLmZ!!j1Z[JmF!3[!%KZrp*1ZKI`B!$qH#!'d%FJ9,"3E34J!2j
U5)8`"@F!!)C63'F!!1*63'F%B!!"0#"8)!E34m(m!!B5-!!NC`4k!@!#3J8J9#!
'd%I"r!!'%M!!*!S"!!%J9#!'d%I"r!!'%B%!*$!ZrpK53$e!rq``,[r@8N!L"X2
%dN!p3IrU-#lrh&0!28$rm$!ZrpC53#)'8N($a0*!28(rlNKZrqUST'!!!,iJ9#!
'd%I"r!!'%M!!*!S"!!&R6#"8)!E34m(m!!B4[!!"!#3`,[rB8N!p32rX-#lreP*
!)JE$a0*!28(rkM!Zrpa63$e!rr!`,[r@8N!L"P*"`m653$e"rqj)E[rUU+4J@L"
8)!E34m(m!!B5-!!NCdSJ9#!'d%I"r!!'3M!!*$!ZrpK53$e!rq``,[r@8N!L"X2
%dN!p3IrU-#lrh&0!28$rm$!ZrpC53#)'8N($a0*!28(rlNKZrqUST#!'d%Fp32r
+B!$p&LmZ!!j1Z[`Z60m3m%jH)&rHr!!+6Y"19[rX51F$##"Z!!K$l[ri5K!Lf#,
B+'i!%M`Z!"!J9$!38d!p32rX3NGJG#"8)!I"r!!'%M!!*'GL)&3J"m(m!!C#-!!
N[%GI`'j3)JH54V*Z!!jG`F!"Cd*)E[rZ-#lrqP*!2`!`,[ri8N!L"j*'`qi!$0*
!2`%`,[rq8d!r!$!ZrrK53#)(NNC53F2Z!!c53$m"U+G)E[rZU+454fN'[Qlrl'q
'60m3`%jH)&rHr!!16Y"19[r+51F2'#CZ!!Si,J!)3UF[,J!1U4FJAbKS!!JJ$'B
+2c`!!kR)B!!#5%KZrr+SLc!Zrr63E[rb-Llrq0*!2!%[,J!12c`!"dKZrq*)E[r
H5'lreUQ03QF[#kPL29rrd%*R,`ZTB$iI,``r"cm%2`C)E[r@6VVqe%*RUA-3(fF
!!CDTY%KZrrbTFM!ZrpC53,"Zrra[D%*R,`ZTB$iI5NG[-#"8)!G63-(m!!B5-!!
N#J%!!@F3,``r"cm%2`C)E[r@6VVqKLm,)!G63$m!U@0J!Q#N)&3J"e0!`I`!"K'
m!!%!*#mZ!!j1Z[85F!3[!%KZrp*1ZK6'B!$rIQ!5-#lreP*!-Llrr**!5-'$aMS
")!463,"&E(C#Cbm,U@!q(ljZrp"X0L"8)!G53#)%8d(53-2m!!B3-"!N#J!!!@F
3,``r"cm%2`C)E[r@6VVpr#m,)!G53$m!U@0J"'!!raSJ9#!(8N!L"&0"dN$$r!!
'%E`!!4!N,bi!$Nkkp)"`"#m!5'lrdNkk&$4J!2lX3QF[#kPJ2KmJ"G"()&5`8'd
%B!$qeL"8)!A34m(m!!B5-!!N#J%!!@GD,``r"cm%2`C)E[r@6VVpK#"8)!A34m(
m!!B4[!!"!#4)E[rU-#lrf&*!2`!`,[r@8N!L"F2'dN!r!6!Zrpa63$m!-#lreP*
!)J953F2'dN!r!DLR5'lrkULNB!$qC#mZ!!ir2!!"5'lriNKZrpj)E[r@UBdJ9$!
38d!p32r+3Qlrc'!J)&3`,[r-`I`!"K)`!#4R#LmZrpj#CkPGB"T5E[r-D3S`,[r
-X'lrbQr@,blrhMmm!2qTA8cI'2"1AL"Ih[`!#Nl36PEqJNMR$aJJEJ!-3qlrm%S
3)YJLf#,B)YJq,J!55NGQ$L"m!!!$8LK32L`!6Q"L5NG[-N+R6VS6(L"I+'J!!L!
-9X"R$VjX!%K@`F!"C`3S9'$X)!aQ#$mmrmJ`(kR*2L`!6Q!X3UG1ZK,X)&mSD!!
#)!a@`'F1[Q`!6PE"`!&R"#K8B1`J$'B)2ccrh6!IUFNpE!!8rST#Th!'`HlqLY"
m!#C)`#m!6VS5BLCI3QFqZ!)J5Pp@`'B))JYA`B!"C`Jr2!!C-"qTb5"6-8F!!L"
6@)K$l!!XF!FJf90!E[T#E[k-3N3pE[k+rS3pI!!"rqaJ!!%N3LlrXMe(rkj#,[k
B3HlqQ#e)rkSpE[rXrl4#Cd(ZrjJ[#%*R6VS54$`I5NCR"Mm'-"qTb3aZrrm!%'G
!3J8`,J!38d!p32k#3QlrlQ!L-#lrlZ9!3IB!m%2ZrlK`!l%*9XMrr'B%HJ&J&&*
ZrqjT#M!Zrqk`E[k#Ep4J!RS"%!9R)%UZ!!K@`F!"CaC#Cd(ZrjJ[##mZ!!K1ZZp
Z'Km+"3!"%!9RFN+R%#lqQ!*!!2p53%M!,`"1ZK&H,9rqP%*R2VJ#)%TI9X"Q#NU
ZrT4A`B!"C`Jr2!!C-"qTb8(ZrTJ[##"ZrT3[%"!ZrTJ#3!$r8N")`#m!6VS43#"
6)!6"r!!')DlqP!!J)&-J"-(m!!C#-!!N8N4J"&*ZrSa5E[rXD3``,[rXX'lqK'm
!rY3`,[k+N!"ZrS`J8c#!,`XJ8h!'`G$3I!!Q5-![!%kk%1"#Ccki!L!m(dT'C`B
r"M!IUFNY5`!860mBm%jH)&rHr!!-6Y"19[rk51F$##KZ!!JJ$'B#B'!J9$!38d!
p32rk3NCJ0#"8)!E"r!!'5V!!)'FL)&3J"X(m!!B[-!!J6VS3G%*R2VJ#)$iI5NG
R"Mm(-"qTb9*'D3DmE[rkEmB[$%kk%&*#Ccki!L!q(dT(C`Br"c!IUFP-ha$!6Pi
ZRdje6PErpNMR$`JSEJ!))!aQ"'!!!3a#4#"8-""63$e!rrC#4f!!!0SJ9#!(`I`
!"K)`!#3+!3!"Cc)J9#!(`I`!"Lm`!#"1ZJrZ3QFqZ!)J1Kp+4@F'2`8`(kR*)&3
J"m(m!!C#X!!JB!!!NP*%5NGH`'m!!)JJ9#)(8d($r!!'5V!3)&I"`!&RFL!(8d!
m!'!5)&3J"X(m!!C+X!!JC`*J"P0'5NCXkNT'9m"Q$L"85UJ!)&I"`!&R!Rcr)&3
J"m(m!!BL9#)'8N($r!!')l!!)"!J)&3J"P*!`I`!"K'm!!%!*#"8)!I"r!!'3V!
!)#"8)!I"r!!'3M!!*&*(D3LqE[rfE`$r)L"8-)3[$#"8F!E"d0"m!#C)`#m!6VS
2)NcI%2"1ALkI6R919[rf51F('#KZ!!Km!4!'C`!!kN)')&3`%&9!28$rpN*(B!!
!c%*R)&3J"m(m!!BJF!!J)""5J#m!)&3J"e*!`I`!"L"`!#!J%&+!,`!J9#!(`I`
!"L"`!#!J8"!3!N!!rcm!)&3J"e*!`I`!"L"`!#!J8"!3!N!!rcm!6VS2!JaI!!&
QCR`")&3J"m(m!!BQF!!J)&3J"m(m!!BD-!!N)&3J"e*!`I`!"L*8)JI$r!!')l!
!)"!J)&3J"e*!`I`!"L*8)JI$r!!'%l!!*"!N)&3J"e*!`I`!"L',!#!J9#!(8N$
"r!!'%B8!*&*(D3LqE[rfE`$r-'!!ra4-haMJ6PiZRdje6PErr#!Z!!KAJ#e!rr`
JE[rm%"")J'XBX(`!$fi53IS!*M)!jNP%33%`%2m+2!!%CJJGI!!"!!aJ"%)Z!!a
1ALkI6R8""Nj@rpK)j`mB3UF[,J!)U4FQAbK6,#X!"#iV!!JpD`!Brq4+KfB),`a
#CkPPB#3J4b"3-"#`E[rNE4)[$#"()&!`%*!!E[rN2`#TC@!',`a#CkPP,`a#CkP
M3QF[$+PL$&m!!@`+,``r2!$rU9eJ"Lm-3QHTA5m'3QHTBdU(CJ`["Mmm!2qTA@!
!!)a#4#"()&!`%&0!28$rf%*ZrpaJ*%*R)%FJ8$!Zrpc"r!!')(!!)#m3U)`k(lK
&E!)i"9*ZrpaT#M!Zrpb`E[rBEp)[,J!)2c`!"dKZrrj)E[rk5'lrmUQ0-#lrq*!
!E[rd@%!k!,T%E3S["Mmm!2qTA@!B,`BJ"*!!48M!JI`!#&4!2`#TC5m'3QHTA5m
Z!!Jr2!!"5'lrrNKZrrT)E[rbUBd[,[rk2c`!rkPG3QX!&%*V!"C-haM`6PiZRdj
e6PErcNMR$aJQEJ!3+Li!$%*R2c`!J%KZrr#TF"!IC`!"%N+R,`ZT&bKI3QF[,[r
bU'T+AfFQ2A`!H2rX2A`!P[rZ3QF[,[rX,blrmNkk$*T+AfB%I!&J"N)'B!*m!4!
'C`!!d#mX!!K1Z[YJ3UG#CbmZrr+SDcmX!"T)E!!F,b`!%%kkq0iTA`!),b`!#%k
kr0i[#dkkrL4#4%+R6VS-%#"I,LJ!!NU(Ca4#Cbm(6VVpa"!IC`*54#"(,K"Jk!a
%!!&[(#m,2c`!"NKZrqK)E[rN5'lrh+Q0,blrj%*RU9d[#cmm!!9)E[rS5'lrj%K
ZrpbTM5mZrq4#CkPG3UG1ZJZZ)&mTD!!#!!`JE!!-,`K#CbmZrr+SDc!I)&q`D!!
'C`SJE!!-+9!!$'$H5'X!%+NS)%8-8!!"CQB[#cmm!!G)E[rS5'lrj%KZrpbTM5"
&,@J!#[rB5'lrf+Ka3QF[,[rB5'lrh+LY%"pR0%+R)%8`+!!15-![!#mm!!!#!+K
B5TpR#L"Z!!J`[!$rB!JJEJ!)-,`!"aem!!%!&'!!!2JJ46!3DaL`I!!(EK*"qJ%
B-J$Q584"!6!3r`Sm!!4A`'B!!0![!%+R)%8b+!!15-%[!5m!3UF[2!!!#!![2!!
!!3#S@b)I)"m[!DKB)KmJ(dU"9m(!!@F!!*S[!%+R)%8[+!!#,c`!N!2rU&JL(b!
I$%%!$9I"`!&RH#m,2c`!!8KZrqK)E[rN5'lrh+Q0-#lriY"Zrpj)`)(m!!)p32r
D-#lri0"Zrpa)`)(m!!)p32rB3QF[,[rN,blrf+PQ-"pV',"m!2pZ%N(k!&Sb!1C
*4%%"-"$r#M`!"'B'3Li!&'!8)'i!#$#m!!%GI!!"!"4J"%)Z!"4-haM`6PiJApl
m!!a1d%!!N"i"!#K19[lm51F2'#"Z!!j$l[r`5K!Lf#,B)YJLf$JZ!")YEJ!8riJ
p42q5$%6rrfmN)!463$e!r[a#4f!5)!IP3#)(j8%YYK$`!*454fN'[Qlqr'rS3Ul
r#NKZra5SG%+R2cc`Ad+RF2m[!+Pm+&m[$$mZ!"Sr,J!B3QHT'bm-U(-[$%(ZrhJ
[#+NB,``r2!!)5'lrk%KZrq4)E[rFUBe#Tbm-5'lrh%Kk#1JI2!!"3QG#Cd*R2c`
!%%+RU93YArpi,``r2!!,5'lrk%KZrq4)E[rFUBd[$$mm!!G)E[rS5'lrj%KZrp5
TM8+R,`a)E[rF5(S)RKmm!!&#Cd*R-#lrfNM!JI`!#$m!2c`!%%+RU93YArpm,``
r2!!-5'lrk%KZrq4)E[rFUBe#Tc!Z!!K)`#m!F!%[!+KB5TpQ##mZrq5T@'!H3UF
`,J!)5-![!(!#,`#S@%UICJS[,[rN2c`!rkPG3UF`,J!)5-![!(!%,`#S@%UICJ4
#"@!#HJ%[,[rN5)8r"DPM3QHSKcmk#!+SL$mm!!'SL6mm!!bSLNKZrfUSLbm-2c`
!"dKZrqK)E[rN5'lrh+Q0-#lri*!!E[rF9d!b,[pXdQlrDM3Zrh$838M!JF*53$e
!rj!!3UG1ZJJ1)&mYD!!#ri3JE[q%5T!!CQ`[$$mm!!C)E[rS5'lrj%KZrpbTM5m
Zrq5T@%*R,blrK%kkqD!3(fFH3UFJE[q%2bJ!"Mm%5'lrm#mZ!"41Z[4b,9rrJ'!
L3UlrJ#m-2c`!"8KZrqK)E[rN5'lrh+Q0,blrj$mm!2qTA@!!!HK#4d+R6VS(LL"
I,#J!!NU'Ca4#Cbm'6VVj2K!IC`*54b"',""Jk%T(CN4#V[q!,``r2!!&5'lrk%K
Zrq4)E[rFUBd[,[rN2c`!rkPG,``r2!!'5'lrk%KZrq4)E[rFUBd[,[rN2c`!rkP
GB!!"HJa(!!&QD%+R6VS('#"I,@J!![q%3QF[,[q%6VVic"!I#J!!!@F+)'lrK#e
3ri4Jj%+R)'lrK$mS!!Br"%KZrr![,J!86VVcN!!YArq!,``r2!!'5'lrk%KZrq4
)E[rFUBd[,[rN2c`!rkPGB!!"$%+R6VS'ZL"I*QJ!!L"m!!!#&$i3)!G%3$i!)!Y
@`'F1[QX!6PE"`!&R"#C6B1`J#fFd3UG1ZJCq)&mYD!!#ri4+V[q%9X"R(#"Zri3
b+!!'XQX!5&E"`!&R#L"Zri3Y82q%B0aJ1L"m!!!$8LC33UG1ZJC#)&mYD!!#ri4
+V[q%9X"R(#"Zri3b+!!'XQX!5&E"`!&R#L"Zri3Y82q%B0a+V[q%9m"R(#m!3QF
[,[q%6VVhb")I)"m+!3!"J!%#3!!"CbT#Tdkk"H`JAbeS!!,rK%*R,blrK%kkpk!
3(`S!!!&R#L"Zri3Y82q%B14#Tb"Zri3r+!!'2`4)E[r`,bi!&%kkmQ3YArq!5Ul
rJ'F),blrJ%kkpPi[$$mm!!P)E[rS5'lrj%KZrpbTM8(kj)3Y52rN,``r2!!*2bl
rk#mZrq4)E[rFUBi[$$mm!!G)E[rS5'lrj%KZrpbTM8(kkUJY52rN,``r2!!(2bl
rk#mZrq4)E[rFUBi[$$mm!!4)E[rS5'lrj%KZrpbTM8(kj))Y52rN,``r2!!%2bl
rk#mZrq4)E[rFUBi[$%kkpa3[$+N9,``r2!!)5'lrk%KZrq4)E[rFUBd`,[rJ8d!
p32r82@lri[rD,``r2!!,5'lrk%KZrq4)E[rFUBd`,[rL8d!p32r@2@lri2rB5'l
re+LK5'lre$mm!!)r2!!#U+N`,[rDN!"ZrpB-3!!#E4C)E[r8U+&)E[r82c`!!Mm
m!!+SU@$F3IVhdLm)5'lrkUQ4-#lrkPY!CbT63'F!!BC63'F!!QT63'F!!`KA3'F
!!`T63'F!!``%3!$cC`!#"'!!!c4#V[mX)'lrJ#"32@J!![m`3QG"l[mD,`K1ZJ4
329rr'%TZraKQ!!%f,blrJ%kkmc)JE[q%,9$rK%UZri4@`'FL,`"#CbmZri41Z[A
!%KmJ(`S"!!(!!@F+)'lrK#e3ri4JeNUZri4Q1%+R6VS$f#"I,@J!![q%5UlrK&E
!Cb)[!%*R,blrK%kkpB)5(b!I#J%!!F!"C`SJE[q%,9$rK'$@5UlrK'C#3UlrJ#m
-2c`!"8KZrqK)E[rN5'lrh+Q0,blrj$mm!2qTA5m-2c`!"NKZrqK)E[rN5'lrh+Q
0,blrj$mm!2qTA@"S3UFJE[q%2bJ!"Mm%5'lrm#mZ!"41ZZrk,9rrJ#mZri"1Z[2
kIJ%JE[q%,""+KQF83QF["Nkkp1`3(fF#8NFJ4L`3B1J-4`!#E"i[$$mm!!C)E[r
S5'lrj%KZrpbTM5mZrq3r2!$rU9d[$%kkp2a)E!!3U5KJ!!(8,blrJ%kkmISJE[q
%,9$rK%UZri4@`'FL,`"#CbmZri41Z[5)%KmJ(`S"!!(!!@F+)'lrK#e3ri4JeNU
Zri4Q+N+R6VS#S#"I,@J!![q%3QF[,[q%6VVd9"!I#J!!!@F+)'lrK#e3ri4Jj%+
R)'lrK$mS!!Br"%KZrr![,J!86VV['#eIri![,[q!6VVc'#m-6VVdANKX!"#T+'!
!!6B3"@F3,``[,[pi2blrN!"1ZZK-B$3[$$mm!!a)E[rS5'lrj%KZrpbTM5mZrq3
r2!!"U@-[$#mZrhJr,[q3!%kkk"i[,[rN3QHTBf!!!1S3"@F5,``[,[pi2blrN!"
1ZZJ!B!!!e%+RUA8J(j!!V[m+,`"#Tbki![!L(b!IXS"[9NKZr`+TFM!Zr`53!'l
r#'S#4%!-3!!$AF"X(M)Zr`+5E[m'DJ*%33a"!!0G`F!"C`JpI!!"rqTJ1LeZr`,
r"Lm-,blrH$mZrj!!6VVVY%+RUA8YArm+B'*)E[m'UA)[$#mZrhJr,[q3!%kkkjC
#TkPe,9rr#Q"%,`a1ZZ1-B$`[$%kkjJjJ0#m-2c`!$%KZrqK)E[rN5'lrh+Q0%!9
R#LmZrq4#CkPMB!S[,[rN2c`!!DPM)!8+!!!"'J!`,[rUDaL`I!!(EK*"qJ#S-J$
Q584"!6!3r`Sm!!4Q!2a`5UlrK'G!3UG1ZJ$`)&mQD!!#)!Y@`'F@)'lrK$)V!%L
bD!!'9X(!!@F%*P0Jj#!,Ca3YI!!!!K6qrM!V!%j%3#"Zr[i`J%UZri"R(#mZri"
1Z[!f$'i!!IrUC``[,[q!6VV[UN+Zri![,[piU98[,[pmU98[$+Q$,blr&+Kc,@l
rJ!!F60mBm%jH)&rHr!!86Y!!#J#3"#*I)"qJ6#k!6R&`!#m*-F!#)%jeF!"JpL*
I)"qK)Lk)6[VrkL*I)&qJ)dlkrq!LAb!I)&qJ*%lkrp3L(b!I)PmJAk!Z)N&1q[r
%)"mLAb"I,`#J1b+!6R8[I!!!!`J!"%je,h`!!!0@!!41G5*I%"mJAfB%S!aJ!U3
-2S"1d5*I)&qJ&ck!6Y&d!L"I2`)[#+hTG!"1q[rd0$`!#Nlk!!Bd2!!-)&mr!Lm
)VHd!N!1S!*!$Z!!!!J)!N!1B!*!$)!8%2c`!!DR`"NJr2!!"UI!'8Mmm!!'Tm!C
F2c`!!DR`"M`r2!!"UI!+)Mmm!!'Tm!SB2c`!!DR`#Rir2!!"UI!+X$mm!!'Tm!S
X2c`!!DR`#c!r2!!"UI!!V$mm!!+Tm!1U2c`!!UR`"3Br2!!#UI!)R$mm!!+Tm!%
82c`!!UR`!TBr2!!#UI!!!$mm!!1Tm!'Q2c`!!kR`!!!-h!#3!`Y19[rk)'i!##m
S!"*)HJ"35(S!6%Kk!%LTLd+R2c`"!%+RF2m[!+Pm,9rrr%+R5'lrqUQ4,blrr+Q
$$'i!!IrkCJC#,J!-B!BGI!!"!!a1ALkI6RA85%9'58a843!#!!"19[lL51F"#%+
R2c`"!8+RF2m[!+Pm+&p#TdKZrrUTN3aZ!!,rqQB),`bTJf!!!)3[$$mm!!0)E[r
i5'lrp%KZrqbTM5mZrr4)E[lXUC!!5'lql%KZrZC1ZJB!1flqk2r`3NGJ4#m-)!G
B3$m!5'lrq%KZrr4)E[rXUBd[,[rd5'lql+Q3!%(ZrZ`J#&+!,`""l[lL,`K`"#m
!6VS&FL!(j8!VV[lL!0"54`a(!!G[YLm-UB0-ha#!6Pj1GG0&9&4C8%96!!"19[k
L51F2#%UZ!!KR!!(k3QF[,J!)U'Sm(d*R,bi!#+KV2Km`"J4!!3"R#&0!Cb4J!!(
@3UFr2!%!U8Nr"dKZr[bT4N*R5'lqr+Qf29rqqQ!!!EB`"e0!D`!"VJa!!!CZ!!'
QidJ`1`!'6[X!N!-3!4`",!%m!@`"FJ'@2A`!C2lf2A`!C2li3UF[,[lf,bhrqMm
Yrr""lIr3,`K#Td+RF!%[!(!#,`#S@b!I2`"1ZJU8+&mJ$'F!!-J[,3!-5(S"U%*
R6VS*4LmY!!`J9$!S!!*)`#m!2c`!!8kk#,![,3!-5(S"J%*R6VS*)LmY!!`J9%K
S!!4#Cdkk#4)[,3!-5(S"AN*R6VS*"#mY!!`J9$!35-![!$mm!!&1ZJK`,bd!$%K
k!5T#Cdkk#1)[,3!-6VS(eL"8-""63$e!rU*#4@!b,bd!$%Kk!3*#Cdkk#,i[,3!
-)&3J"F(m!!BJF!!J,a"#Cdkk#+B[,3!-6VS(QP*&D3DkE[kLEmJ[$%kk#Ej1ZJR
DB!!!JN*R3UFr2!!"6VS$aMJIB("#Cd+R2c`!!Nkk!lBi(f"J5UhrqQBB3UFr"UQ
r2`FI2!!"U89"q[dH+dMrqQ"#3UFr"UQr2`G#CkP&3UhrqQ!`6VVpBQ!U2A`!C2l
f2A`!C2li,blqpNKk!%4)HJ!X3UG)E[kN6VS$I'!''h`!!Irr3QHT1%cI%2"1ALk
I6RA38Np$490663"3%NaeBA8J4R*[Cb"(D@*XCA4c2`!85'&fD@jR)'CeEL"hDA4
S)(P[GA)!!b#3!a!JCQPXCA-JFf9XC@0dC@3T!!)J+!!#1L!!"eC[E(9YC5"19[r
Q,`FJEJ!)3qlrm#,B)YJLf#,B,@lrq[rS3QF[,[rS5'lrl+NX2Km`"e0!C`j63'F
B8d"R)&9!CaaJ+%+R,blrk+Np6VVp5'!D5'lrm#mZrqbTXf!12`F[,[rk2blrrNk
Y!)SZ(djH,Tp1GF4&38aA9%K0!!"19[rZ)'i!#%2Zrr!Lf#,B)YJLf#mZrr)[2!#
3!i"1ZJFm)"mp32rZ3UF`,[rq5-![!#mm!!!"!+KB$*m!!!%!CJa#TcmZrqkT2Nk
kr-a1ALkI6RA%48&-9e4)5`!!6PErk+Qd3QFr22rr5'lrm+P`(9rrla!ZrqpRD$!
Zrr"63'FL98"R+&G!Ca463'FU8d"R!Q"12blrrNkY!(TJ4%kY!**J2NKZrr"1Z[l
HB$4)E[r`6VVr6'!U3QF[,[rbU'T+AfFH2A`!H2rU2A`!P[rX3QF[,[rU,blrmNk
k!FSpArrS%#hrrfF!rhT1ANjec8&*6N9@48i!!%kk"#j19J!!,&p193!!Rqd!%%k
k"$""lIr-,`LSENkk!141ZJ%q6VS"1Nkk!6C1ZJ%bU2kT%UN`UFa#TkPlU&!r2!$
)2c`!8%kY!+SlI!!Srr)lI!!+rr3lI!&8rrBlI!(drrK)EIrb5(S!GN*R(c`!!6m
m!!%r2!!*6Ud!XN)Yrrp#VIrk1hcrN!2`2ccrrd*R)"qJ-N+R2c`"!+Qr,cS!2+P
03UFr2!%!UEp#CkNe3UFr2!%"UEp#CkNeU6G1Z[kQ6VS$M%jG6VS$H%je6Pj1GG0
'694&8e3J!""%8PC5#e4PFh3J9fPZC'ph)PmJ(k"-,S"1FA!!,`Na`!)J6R9`!'$
f5MJ#MQSU)(J"-%2i!43J#*!!NA3-X)*P&L*4)FJ"&#+!3K%LH!+U)SJJJY'T!!a
1GD"M6R8LAb!IS5)ZL%lkrlBLAb"IS#01q[qX)PmJAk!T6[VrSL*I)&qJ+NlkrjL
J0Nlkrj3L(b!I)PmJAk!Z)N&1q[q#6PEr`%(Zrm!aEJ!)!"BKEJ!+!"+J&ce!!!j
1AL*IA)p1dA3")&mr!Lm)VHTd!L"I2`)[#+hTG!"1q[rd)'m!#$mm!!'TlL"[!!3
JJ%lk!!iJE`!%)#m!#%*RUHiJAe"26Y"19J!!,&p)jr$i3S-f!'m!!9JN5#C*)JK
Q8NSi#20R"Nkk"3+SrNKk!9+SEdAk!8`eI!!%!%3eI!!*!%T)HJ)+U)Y"qJ)%-#J
!"Y"3d'J!!M&!!!Jr2!!'2bJ!#+L63UHSf%(k!HiJRbY+!!`L95K4)ST"qJ%'XFT
R+N(k!GC+N!"Q)NKk!F5SLd(k!Ei`+!!'d&$3D!!#-8!!#%+RU0K"qJ'b)*m-%`!
0CJ`r2!!'2bS!-+L6B!B-%`!+CLi`1J'5-J$3DJ!`@%#`DJ!8C44)DJ!33QG%36m
",cS"H+M[B!!!K%*R2`'SP'"k$"-!"fB)2c`!&+R)B'a"qJ&1FJ!b+!!%`X04Mb*
2$"-!#'B8-#S!-T!!360!!!)cDJ!b!!Bf!@!3-fS!-J!#-#S!-Y""-d!!"M!U!$#
3!&!bJ$!U!$$3D!!#-d!!"%K4U+03M``6!!KQ#N4$2`0#CkL8B!J[#d*R2`1SK5*
9)Sa-ham26R91ANje*8p99%0)3dm!N0a#V`!33IS!###[!!41G3#3"%je)&p1A5m
)6VS#c%je)&p193!!6Y"#Cbm!)#m!"Lp!!!3rHJ!)!!JJ(dje!!"19J!!,&p)jm$
!)JKQ-L)k!*!!CL)JHJ#'6VVp8#))CJK"qJ"k)+d!$%(k!'SV5!!-+dJ!#'"-,`N
r!#""6T!!B%)-%!!"CKSL1J"BCJSJHJ"16VVp''!X,`Nr!#""6T!!B#,4r!#3!a"
)`#&!!#4#U!!Z-A`!!`!X)8N!)+!$3IVrJ$#!60m$!dje6Pj1G592994$5&-J!3#
3"J%!N!JLAd(krr3JRdl4)Pp"q[rZ)*p1d8j@!!!XAb*I)&m[#A!"(c`!$5*26VV
r-K!I6R91ANje*9GI6%iJN!0)jd"J*%mb!%K!-$`J)1**C!*536m!8d&ZqL*25%"
1Z[m!,NT-h`B#6R919J!!,&mLAc!I%KmJAbm*8d"["%kkrm!I!5*2F!&1Z[l@%"p
1G8jH6R8P9ep$)*!%6PB!!#aI)Pmb(b!I)&m[#8MR(J!L6jrm!*!$$%*#,!"X!N5
!*J!S!d*%5%5)r!!++J3k!iVm!!T)4#B%0J9)43C&!$!6"9*#5S0QfNU'E!C53K-
m!#df!CC#E`B`!dkkrd3`!NkkrPlIr!#3!`a-h`"i6R91ANje*9GI55#3"%j@!!!
XAb)I-"mLAb"I,`&#34)CX%&Z"NT!E`KJ#*!!38kkr`)`!8kkrKa1G8jH6R8P9ep
69&)J)%j@!!![!#m")#m!&#)[!""1ZJ!8,d!!&#)I)"p1ALpA!!4BMdje6PB!!#a
I51Fq!#S!DJ*%J#`"DJ*%J53"5%*+3QBF0J"#3%K!C`5!`63!5%)`!i$"0!!L!N*
!5%"J(#3!*J&#J%+"H"r8JY'!dS'`Jfd%N!#$8J&4c2r`5S9U!N5!ZiCU!N5"60m
!I%je6Pj1G8P%59C06d3J6PB!!%(Yrm`[#+KZ6Pj1GFK*6NP84e*"!!"19J!!3Uh
q'N)YrK4#,Ii93Uhq$N+YrJT#VIhq6Pj1GD9*6NP85%9"!*!'#P0'68GPG%CTE'8
!8dp'9%kk!$C1d8kk!$"1k3!%6VS!+%lT!!K1ZJ!J6ZN!$%kk!"K1k3!33IVra#"
3S#T"q[qm)&#J58je3IVrXNU3!'F))&"+N!"Q,+!M3UF[1[q`5(VrS+QK)"pQ"M!
m!'1Tb8(kri`JJ#m!UC*"q[q#)&!L8%je#*!!!!B)d!!()P"1G3!!#3J!@!!'6PE
rr#em!!!*m[rm)'lrr#e3!!K1ANje6PB!!#m-+'i!#$!X!!53!&3b,!!#NQ`!"V*
!EJC#,J!-B!BGI!!"!!`SAdjH,Tp1G8j@!!![,IlqU(0#Cd*RU(JJEIr-5'J!%+K
l6Pj1G8j@rrJ[,IlqU(-r,IlD2bhqf+Ki)'hrc%2ZrrK"k!!3)YJLf$!Zrrk3!(`
!$ce!rri`,[rmN!"m!!mp32rm5'lrq+Kl6Pj1G8j@rqj)j`%)5'lrlUKd6VVrM#"
Yrma$l[ri3HJ!%#,B)YK#"f!N5)FJ"d(YrYcP3#K`!!!3,J!*!N!!!@F',`bT9f!
%,`bT@&)($!F!!@r@,bhqrUN%,blrlUKc60m3J%jH)&p86dl36PErf%MR$`Jm,J!
)5'lriUKd3J9+4Qm!!8"#"bKZ!!Sp4[ri5NCH`$)YrZUbEIlkAF(!!5)(#J%!!F!
"C`!!N!!JEJ!+'""64P+Z!!T)K!a%!!eQ"(i"B()-"!!)CaSJEIld)&!`,IlUd'h
ql%L%%B3!!&*YrZTJ8NTYrZT[5NkkrY*)E[rB-#hqdT!!EIlN2`!`,Il3N!"YrZB
r!$mYrY)`,IlSd'hqd*!!EIlQ2`#STc!YrY+3!'hqj$Y!rY*)E[rBU+06EIlUHJ&
J!RS"B!$r@#!&#J!!!@FX6VVqHMmYrY)r,Il3U*-[$%*R-#lrq*!!4NL(N!"(2`#
SK8KZrqDSQ#YZrqEqd$!YrZU`EIlkA-#!"`*!!!&R3#"Yr[!J8$!YrZa)`)(Yr[V
M3$'YrZS!!%kk!ra+4Pl!)JF+!3!"`!&R&L"Yr[3J8$!YrZ`4[2r*!!!lI!!"rZT
J!2kq)'hqm#"3-#hql%M!JHhqqZ0!-DhqkJ!!,blriUKc60m3m%jH)&pF6dl36PB
!!$!Z!!K63-(YrZM3I!!+28!!#NjH)&p86dl36PErq%*R,bhqh+PJ29rrr$!YrYL
3!'lrr$e!rrK#CbmYrZ#TB$eIrri`,IlDN!"Zrrip32rk5QlrqPE!5Qlrq&E"J!&
R+Nkkr@`JEIr-5'J!%$mZrrSr,[ri,bhq(UM[+flrr2lB,bhq(UNR6VS&I%jH6R9
19[rN51F2!(S+2LhqlL!(5-#"lIlk2!!pEIlmrq4i!@")2c`!"6m&U*-[,Ild6Ud
!-L"Yr[3[%$m()'hqm#"3)!EM3$m`!!#SK5mYr[41V3!kfQhqk0jYr[T54VjYr[K
Q"%*(3NC54'N'Z'lrj'qb5'lrjULB+flrj[l360m!m%jH6R919[rU51F$#%KZrqU
SG%kkr)j#V[ri3Ulrr%KZrrLSHd)(B!!!kNL()!G"lIlFj8!SF!!!)'hrc%2ZrrK
"k!!3)YJLf%L()!IM3$)f!2K638L()!IM3$f"!2K)Kd(YrVi3-(!!5)$M3$)f!2b
5I!!25)G"lIkq%$"`!%L!id!pJ3$i5)FJ"q0!-MB!r**m!!j)Kb!(id!pJ3$m5)G
"lIkq%$"`!%L!id!b0J$idR`!%%L(3Hhq[K!`F!")J10!2B%!r#m-2blrqMmZrrL
T@5m--#lrrT!!E[rk2`!`,[rmN!"ZrrJr!+PF5)FJ"d(YrY6M3$)Zrrb5E[ri0$!
!!*4"2!*+4Q`#3NB[$$m'U@95"``(!!&[!2m56VS!&%kkrHi[,[rUU(0-ha$!6Pj
1G8j@rrJJEIr-,@J!&2rm-#lrr*!!I!!228$rq$!Zrrk3!(`!$ce!rrT)E[riU5K
1ANje6PErjNMR!3K)E[rQU(3`,J!19d"R!!#@8d"R$&0!CaT63'G`B!!"!#mYr[i
[,J!+5'hqb+NPB!!!lNkkq`BJEIlq5HJ!%$!X!!D3!'`!!Me!rq``,!!%N!"828$
rkN+R,bhqrLmZ!!T)EIl!U5XYArrd5Ulrp'FD6VVr@#mYr[ir,[rf2blrp"mm!!'
T(8kkrKCJ!!#83QF[,Ilq,bi!#UNH%"pR"LmYr[kT&Q"k3UHT*#!Yr[k`RfCS6VV
kL%KZ!!USF8*R,bi!#LmYr[j)E[rZU@`q(dT(CdB`"`4!!"4R&&0!Ca"63'F-8d"
R#!4!!'TR''!b3QF[,[rZ,bi!#N(k!ES[#+PS2KpJ(%*R,blrlLmZ!!T#TkPS2Kp
1Z[b5B!B[,IlqU4m[,[rQU(0-ha#!6PiJAe"26Y"19[rZ5'lrr+Kd5'lrpMmm!!8
r,Il3U)![,[rf6VS!RMYYrZlql%*YrZSJEIl`)&!`,IlX5-#"lIlkid!aVIlU!!!
`,Ilkd@hqlM!YrZk`EIliCJ4#EIlZ5'hqd$mm!!9#CcmYr[a1Z[[`U)"1Z[Qk5'l
rlMmm!!9`#T!!EIlQ2`!r,Il@-#hqk0"YrY#3!'hqjMm!U+G)E[rZ3QF`,IlS4%!
r!#mYrKkSlbmYrKkT*dkk!Di[,[rmU(01ANje6PErqNMR!3K1Z[P%5'lrr$mm!$)
r,IlSU)!JEIr-5HJ!%$!Zrrc3EJ!)-L`!"**m!!md,IlBe%'3!%)q!%T(EKJ`,J!
)N!"Zrr`b,IlBdP53!%%q!%T(E3*#4bmYrYa#CbmYrYbTB#!(d&mr!+PM-#lrrY"
Z!!Sb,!!'NR`!$c3YrYV83C!!3Mi!5NGZ'M!Z!!U3!'lrrM)YrYV5E!!#N!""2J"
+4fd#3NF[,IlJ3QF[,IlJU@!J"p"I2`#TBdkkq`"-ha#!6PiZRdje6PErm%MR$aJ
QEJ!+5Qi!#'F!!,i-EJ!8!!KA`!aZ!"B!#&I"J!&%!"`!3QF[#kPJ1Kp#Cbm,U@'
kAel!)JE#!#!'#J!!!8MR`!"#Cbm,U@)d(dcI!!1d49l#`!+#!'G`)&0$l[rb8)J
Lf#,B3QG)E[rb6VVhj"JI$'i!&J!)9m!-EJ!A!!KA`B!"Cb3JEIlq5HJ!%%L%)!6
M3%L%)J6M363d!!58G"!!P'hqk$i#B!3q,IlS%!CR"L!(4%!q!#m,)!A34cm!U@0
1Z[SU6VVhZNcI'2"1AL"IA%p1d%j@rr4)j`!B5'lrr+Kd3UHSf#KI3UG1Z[G-*Pm
[#bm-U0`[,IlqU5*1Z[H#)'hrc%KS!"#SSbmYr[kT"#mYr[kTD8kkpiC1Z[T#,bh
qrUNM,``[#kMF,`bSf5mZrrbSFdcI'!"1ANje!!!$+!#)!!*19[rm,`G#VIlq1fi
!#[lm1fi!#2lk-#hqqX(Yr[`l32li3UF`,Ili5-![!%kY!%)VArld5Uhqp'C5,bd
!$%Kk!5K#CdkY!()[,3!--#hqr%M!,`!r2!!"6Ud!BLmY!!`I2!!U2c`!!8kY!&S
[,3!--#hqqNM!,`!r2!!"6Ud!BLmY!!a1V3"UB!!!P%+R-#hqr10!5-![!%kY!%)
VArl`5Uhqm'Bd,bhqp%kY!#S[,3!-5(S!FN*R6Ud!FLmY!!``,Ilm5-![!$mm!!&
1V3"L,bd!$%kY!'TJ4$!Yr[a63$e!rra#4f!5)'hqm#"3)!IM3%*`!!"54fN'[Ql
rr'rS3QhqlM!Yr[L3!'hqqMY!rZa#EIlU'h`!!Ikq3Lhq[biI6PiZRdje3%j[G#"
PEQpeCfJJE@9YEh*j)(4[)'&XE'pMBA4P)(4SC5"%C@*eCb"AD@jNEhFRFb"-D@j
P6'9Z)%&bFQ&j1L!!28j[G#"PEQpeCfJJE@9YEh*j)(4[)'&XE'pMBA4P)(4SC5"
%C@*eCb"AD@jNEhFRFb"-D@jP)%&bFQ&j1L"19[lJ,`FJEJ!83qlrq#,B)YJJEJ!
33qlqq("!)YK63'lk5'lqi+Kd5UhqrQB!!6a#Td(YrL)[#%KZrrK)E[li(bi!$%*
RF2m[!"mZ!!j#TkN6+erqrNKYrXJr2!!%2c`!'$!YrejC3$m!-#hrA&P!2`#STdK
YrX!r2!!82c`!&$mYrei`,IpFN!"m!"3r!+LR3UHSf#YIrKj)EIl32c`!"8*R2bh
qr%kY!++SJ#mYr[kSFcmZ!!USKcmZ!!LSLNKZr[#SLc!Zr[,3E[l`-LlqpY*!1d(
qk$YZr[$qjMYZr[6qj%KYrY4`"H0!-Llqp-2Yr[V53$m"F!VM3$)Yr[c$lIlSdN!
r!DL!3JGJ2NL()!G"lIlFj8")ji#!3UF[,Ilq)QhqrNKT!"")HJ"53QG#Cd*R2c`
!!6mm!""#TkP8)Kp-h`%")B%!!&)($!F!!@qm3Uhqf%kY!))[,Ilq6Ud!8N(Y!*S
[#%kY!%S[,[lJU(-Z(djH)&rHr!!36Y!!N2m!N&8"!*!$3J#3!d%!N!6Y!!$-(!!
q!*!$(!$L!!4%594-!!)!+N4-6dF!!J"1689193!"!(*66dC8!*!$LN024%8!!`#
@!3$rr`#3#!%"rrm!N!0H!*!%m&rrr`!!!KX!N!3"!2rr!!!"6!#3"!%"rrm!!!&
P!*!%m&rrr`!!!aN!N!3"!2rr!!!"IJ#3"!%"rrm!!!'6!*!&!3!!%!!$-J!"(#`
!!2rr)!!Pl!#3"3(rrc3!*TJ!!4b!!!,rrc!!-hJ!N!8$rrm`!$b%!*!%#P0'68G
PG%CTE'9cT`:
!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting telnet.Hqx...
cat >telnet.Hqx <<'!E!O!F!'
(This file must be converted with BinHex 4.0)

:"R4PE'jPG!""8&"-9%a19#!!N!H`!)ll!*!%!3#3!ki!N!1Y!*!$!FS!N2-Q*94
&6%j&9#"@CA*cD@pZ)$%Z-L`J-6!J8f9`G'9YBQ9b)$%j1$8!N!-+!*!$!J#3!`'
!!*!%"d&38%`!N!BF9%a19!#3!`&*3diM!*!&J%C548B!N!@!!*!$$!"3!'3!r`'
N!Ecrr`#3!``!8!"N!2m"T!-*rrm!N!1X!!-!N!93!&S!D3#@"!0C49-!N!C3!2!
!D3%X"!*16`#3"38!&!!C!A+)-9P[G5"SBACP)(*PBf9TGQ9N)'%J9%C88#!SCQP
XC5"dFQ&ZFfCPFLNJFQ9aG@9cG#i!N!BC!"3!5`&bL$T8D'8JD'pcG#"H-#"hB@j
dFb"dEb"H-5"dD'8JCQPXC5!LAM)L,L!J5A-JG'KKG#"KE'`JFQPRD(3r!*!$5!!
"!*!&#J!+!"N!m)JC35"1CA4hEh*V)%9bFQpb)%KKF("PEQ9N)3#3"P!!#J#(!2#
)$e0PG#"LH5"6CA4*9'9iG!#3"*`!!`#3"3S!-J!S!'i%!Np,!*!&#J#-!#J!b!3
'3d&13d9-!*!&F`!+!))!m*!!%N4PCQ&eE(4'Eh*PD@GZ5'pcG!#3"6)!#J"N!2#
)5&"XC@&cC5"dHA"P)'PZ)(4SC5"ZB@eP)'pb)'jeE@*PFL"[CL"dD'8JCQpbC@P
REL"SEh0d)(P[G5"hB@jd)(4[)(*PB@0S,J#3!f!!!J#3"B`!EJ#P!0)%"&&9593
!N!8+!&S!0`%XL#j")'CKG'&X)'9bFQpb)'KKF("PEQ9N)5!J9'KP)("bEfGbB@d
JGfPXE#"ND@8Z!*!&8!!8!)F",)J#AM!!N!1d!!3!N!@-!'i!T3$5"!4)38a8!*!
&#J"D!$F",)JT4Q&dB@`J4A*bEh)K)%%JG'&cDb"[GQ9bCQa[Gf9N)'PdFb"cG'&
MDb%!N!C3!"3!A`%XL"C6G'&MDb"3EfPZG'9b)'&LEh9d)&i`!*!&C!!8!(-",)J
B9'&cDb"MEfjdFQpX)'*XEf0V)'&d)&ia!*!&H!!8!)F",)J29'&cDb"ZB@eP)'P
c)&ib!*!$!K)!&3#3"3m!Y!!H!3Q)"A"bC@0f!*!')`#d!$)"#BJ%F(0ZG!#3"6F
!Y!"'!3Q)"A"NFQp`!*!'5`#d!&S"#BJ&BQ4MD'X!N!CI!,3!EJ%*L!CeER"bEh3
!N!9c!,3!JJ%*L!CLC(CPFR-!N!@(!,3!PJ%*L!9LC'aPEJ#3"TX!Y!#U!3Q)"R4
dE'9iF!#3"Dm!Y!#q!3Q)"@CbB@Gc!*!'``#d!0)"#BJ%CR*PC3#3"GF!Y!$Q!3Q
)"'CKD@`!N!82!!S!(J#UL"&3B@0VCA4c)&*PBf9TGQ9N1J#3"L-!#J!b!+U)$9"
KBfYPG(-J8f9ZG$S!N!Bh!!S!4J#UL""3B@0VCA4c)%4bEh"`C@3k!*!&5`!+!&S
!USJ13Q&N)%0SC@0VFh9YFcS!N!9I!!S!EJ#UL"49EQKKEQ4XC@3J8(*[G'pMEfa
c1J#3"A-!#J##!+U)$8*KC#"@CA*cD@pZFcS!N!D(!!S!PJ#UL!a#B@3J6'9ZCh4
SFcS!N!@E!!S!UJ#UL!a89%`J4AK`DA*PC$S!N!@[!!S![J#UL!T'FQ&RE@9ZG(-
k!*!&``!+!0)!USJ04R*PC5"3B@0VCA4c1J#3"YF!#J$Q!+U)%%aTFh4PEL"'B@P
XGA*PFcS!N!2`!!i!N!8+!!S!'3"'L!9-Ef0KE!#3"JS!6J!C!)U)"dC[FQ9TCfi
!N!B+!*)!'3$1L!4)Eh0d!*!&#J$@!"N"&)J(5'&ZC'aPFJ#3"JS"(!!C!9L)!N0
Z!*!&(J!+!#d!4SJ!N!BH!%i!,3#+L!#3"Ki!NJ!Y!-k)!*!'(J$@!#d"&)J!N!B
H!4`!,3&BL!#3"M)!#J""!%D)!*!'-J"1!%%!LSJ!N!Bb!*)!33$1L!#3"M)!eJ"
"!45)!*!'-J%F!%%"@)J!N!3V!&S!-J$)!EJ!!3%!N!J0&80[EQCTFQdJ4QPXC5"
8FQ&ZFfCPFL!!N!-I!'3!I3$e!AF!!`#3#6-*4A*bEh)J3Qpi)!#3!bF!C!"p!28
"G`!"!3#3#$i46Q&YC5"'Eh*PD@GZ)%K[Fh3J!*!$)`!b!'3"*`&h!!3"!*!)6Je
*8#"6G'&dDA0dD@0c)!#3!b-!C!",!2S"U3!%!3#3#&i19843)&0dBA4TFh4TBh-
!N!0T!3#3#Iq3"!46C@jN#Na[Bf&X)%9MD'm!N!33)N&bC5"CEh8J9'KPFQ8r)J#
3"!iL3@*[FR3J6h9dF(9d)J#3"!FL3R*PB@XL!*!%$89iF'9NDA4P)%4KG'%!N!K
S!3%!N!MrN!2I#%0[E@eKEQ4c"dp`C@iZN!-!N!3&3fa[Ff8!N!338f9ZC#"*E@e
PC'PKG'9XH3#3"!a84P43)&0PFRCTBf8!N!3'6'PcG'9Z!*!%"&&eDA3!N!L&!3)
!N!MrN!20"&0SEhF19%03)&0dBA4TFh4TBh-!N!366ANJ5@jdCA*ZCA3J3@4NFQ9
cF`#3""40H5""F("XCA4KE'XJ3@4NFQ9cF`#3""C"Bh4TGQ8J9843)%0[EQjPBh4
TEfjc!*!%$8P3)&0dBA4TFh4TBh-!N!F"!*!$2rjrq#!#3!JMiNr)*"*3+#R+8#J
U+P!S+UT3+#SU6mJTbN!2j"*"k#2L3!rJ!Rri)!)J%#Gb)&!P8Mr`*h)!3#!#!-!
J!J#!,rS"J#UUrrmUUS!",rUUU5J+J!%S#T99,rU!!5!#rrmL)J!!*h)!!#)L!!!
J!J!!2ri!N!BrrRri2rjrrrrqIrrrrRrrrrjrrrrqIrrrrRrrrrjrrrrqIrrrrRr
rrrjrrrrqIrrrrMrrrrirrrrq2rrrrKrrrriIrrrq(rrrrKrrrrlrN!2qrj!$r[q
3!rlrN!2qrj!$r[q3!rlrrcrq!!!rrJ!!2ri!!$rq!!!rrJ#3"J1J!!!$X!!!%Li
!!!13!!#3!b!$9$mm!!'Tm"ak2c`!!DR`""!r2!!"UI"9QMmm!!'Tm&@D2c`!!DR
`9CSr2!!"UI!4hMmm!!'Tm")@2c`!!DR`&9!r2!!"UI"9U$mm!!'Tm!Sd2c`!!DR
`"*3r2!!"UI!$p$mm!!'Tm!5%2c`!!DR`"$`r2!!"UI!0#$mm!!'Tm!bD2c`!!DR
`%pir2!!"UI!5cMmm!!'Tm"1#2c`!!DR`"DJr2!!"UI!&EMmm!!'Tm!A%2c`!!DR
`%bBr2!!"UI!IqMmm!!'Tm!B32c`!!DR`#T3r2!!"UI!NbMmm!!'Tm#!F2c`!!DR
`)LBr2!!"UI!T&Mmm!!'Tm#9D2c`!!DR`*6Jr2!!"UI!Q*Mmm!!'Tm#8N2c`!!DR
`*P)r2!!"UI!QBMmm!!'Tm#E`2c`!!DR`(T)r2!!"UI!I@Mmm!!'Tm!1d2c`!!DR
`"!Sr2!!"UI!+'$mm!!'Tm!2q2c`!!DR`!qJr2!!"UI!5B$mm!!'Tm!5k2c`!!DR
`"3)r2!!"UI!&&$mm!!'Tm!6`2c`!!DR`"8Sr2!!"UI!&*Mmm!!'Tm!5S2c`!!DR
`"-`r2!!"UI!&1$mm!!'Tm!9F2c`!!DR`"0ir2!!"UI!$UMmm!!'Tm!CZ2c`!!DR
`"V!r2!!"UI!'6$mm!!'Tm"Kb2c`!!DR`9D)r2!!"UI!@GMmm!!'Tm"E`2c`!!DR
`&E)r2!!"UI!9LMmm!!'Tm"@H2c`!!DR`'B!r2!!"UI!C6$mm!!'Tm"RD2c`!!DR
`'5)r2!!"UI!BSMmm!!'Tm!Kd2c`!!DR`#*Sr2!!"UI!),Mmm!!'Tm!Pi2c`!!DR
`#Dir2!!"UI!(%Mmm!!'Tm!P-2c`!!DR`&%!r2!!"UI!aEMmm!!'Tm$jf2c`!!DR
`1m!r2!!"UI!8($mm!!'Tm!#Q2c`!!UR`!#`r2!!$UI!",Mmm!!1Tm!&U2c`!!kR
`!"Jr2!!$UI!!AMmm!!1Tm!!!2c`!!kR`!(!r2!!$UI!"YMmm!!1Tm!-d2c`!!kR
`!I3r2!!$UI!$FMmm!!5Tm!%q2c`!"+R`!!!r2!!%UI!#6Mmm!!5Tm!Ib2c`!"DR
`"qBr2!!&UI!(f$mm!!@Tm"-Q2c`!"UR`%U3r2!!'UI!6"Mmm!!DTm"%!2c`!"UR
`%fSr2!!'UI!3FMmm!!DTm"*m2c`!"UR`&8`r2!!'UI!9BMmm!!DTm"8!2c`!"UR
`&'Jr2!!'UI!!!&A3!*!$98j@rr4#CbmZ!!LSDMYIrp4#CbmZ!!LSDcYIrpB`,Ir
88d"R%J4!!2pR1P0!CbC63'G#B!!"&LmYrr!r,Ir@5'hq`UP'3QG)EIl#UEBlArr
HB!!!q$mYrpB[,Ird6VT-UQ!!!1Jr,Ir@,bhrq%kk8"aJ!!$B-#hreP9!C`K63'F
XB!!!b%+R3UG1ZLA)5'h`(Nkk+Ca#TdKk!1C1ZP8'3Hh`(Lm)6VS,T'!!!+"#CdK
Zrr4)E[rf6VS*h%TICJ!!M%(Ym"j$qJ#Z)0NJf6#4-#lrpNM!,`")EIl#6VS&r%K
YrX*)EI!H%#h`(J*!!2p53$m!6VS'jNKk!(*)EI!H%#h`(J*!!2p53$m!6VS'cM!
Zrr4)`#m!5'hq`Nkk"Ea)EIl#5'h`(K!Ym"i#3!$r8N!r!%kk"UC#TdKk!"T1ZP4
N3Hh`(Lm)6VS,!N*RU6K1ALkI6R896ANJ3A"`E'9dB@aV)'&NC(*PFh-k##`J6Qp
NC6SJ!!P1CA4hEh*V1L!86ANJ5@jdCA*ZCA3JB@4NFQ9cFcS!6PErpLm(UE4#Ccm
mrrp)EIrJUA!3(fF!!Ba#CdKYrq#TIa!ICdj#CdKYrq")E[rm5'lrqUQ!%"m+!!!
"CcB-E3!"rq"Q%N*R5'hri%kk#DJ3(`S!!!&J'!aY!!Mri'B33QG)EIrJ6VS*hK!
I#J!!!@!!!8!`,IrJ8d"R(&9!C`!!Z&9!C`!!XP0!C`!"#&9!C`!!lQ!!!4a#Cbm
YrqT)EIrBU5`lArrF-#hrh&0!D`!!KJa!!!9ZIZ0)-$X!"Nll!*!$$J!X!%J!1!"
)!(B[,Ird6VT-dLmYrrK1ZNpN3UF[,IrUU6e1Z[f%B%T)EIrJ,bhrf+QcB$i[,Ir
B,bhrkNKYrm5T*@!Z)#hrf,#Yr'4Q%MmYrp`[,IrU2bhrlNkY!jTJIN+RU53J,Ir
BX*pR"LmYrpLT(f"k3UHT*#!Yr'5`RfBk3Hhri#m)5'hq`NkY!hS3,Il#!N!!rce
!rrCq!@!@3Hhq`K!`F!!#3!$r2`"1ZN+@8NGT"VjZrrC[j'!b)#hriV#Yr'4Q#$m
Yrqj1V315B"iJ,IrLX+hmC'B%6Ud$UQ!15Qhri'B)6VS5ANkY!k)3,Ik4C`$q6Li
I6Pj1G8kk!j419J!!,&p193!!Rqd!%%kk!jC1V3,+3Hd#bLm)UI&1Z[iF6VSQR%k
k&[*1ZJ1'6Pe1ZJ0b6R91ANje)PmJ(k"-,S"1FA!!,`Na`!)J6R9`!'$f)PmJAk!
Y6[Vrl%Si!SjU+L"i!6"$q!%8)!L3!*&d$,##C4BL85()!43LJ%)4)RJ#UL+))),
4U3!-6R@JBdje)PmJ(k%H,SK1q[qX)PmJAk!I6[VrSL*I)"qK)Lk)6[VrPU!f6[V
rNL)I)"mLAb"IS#iL38lkri!LAb"I,`QJ-cp!!!41G5*I)&m[#D!d2d!!"%je6PB
!!#"Z!"!LEJ!-F!!3'%K!%"P+,J!)C`j+,J!+C`5N2'!3S$aJ$%SZ!!TR"+BmB!+
L2!S!!!%G3!!86PiJAprm!*!$$%l3)Qm!"#"[!!LTEb"I8%p1d#*[!!3JE`!)U@i
r3!!-)&p36dl3)Pm3(b"ICJ5J!'!#T!!qJ%l4)Pm3(b"ICJ5J!@!#T!%qJ%l4)Pm
3(b"ICJ5J!Q!#T!)qJ%l4)Pm3(b"ICJ5J!f!#T!-qJ%l4)Pm3(b"ICJ5J%f!#T"-
qJ%l4)Pm3(b"ICJ5J#'!#T!JqJ%l4)Pm3(b"ICJ5J#@!#T!NqJ%l4)Pm3(b"ICJ5
J#Q!#T!SqJ%l4)Pm3(b"ICJ5J$@!#T!dqJ%l4)Pm3(b"ICJ5J%@!#T"%qJ%l4)Pm
3(b"ICJ5J4'!#T%3qJ%l46PErcN(ZrmiKEJ!1!")aEJ!-!"C#+!!D3LJ!'d+S!"b
J!#*Z!!JbU!!B28!!%NjH)&rIr!#3!`T1d%j@rmj"l[r1-@i!#!!BS!%p3!!+6Pi
JAe526Y"4`@!#8-&19[r13HlrcL&Z!!J!)$&Z!"!!'#*Z!!`K83!N3QJ!,%+S!#j
+!@B%S!*J!U!$28!!%L*Z!!`LU!!S6PiLAprm!*!$#Nl46PErX%(Zrl!KEJ!1!")
aEJ!-!"C#+!!D3QJ!(+!-28!!%N(S!#!LEJ!)-$`!%+!Z6PiLAprm!*!$#Nl46PE
r`%(Zrm!KEJ!+!")aEJ!)!"DJ&6e!!!j1AL*IA)p1d8j@rl""l[q`)@i!$J!5-@i
!$!!@3LJ!'N*S!"bJ$%2S!#!JEJ!)-$`!%+!Z3HlrX+!028!!%NjH)PrIr!#3!`T
1d8j@rmj"l[r1-@i!$J!B-@i!$!!X)@i!#!!ZS%3p3!!36PiLAe#26Y%JE`!%)#m
!#%*RUHiJAe"26Y"#V`!33IS!###[!!41G3#3"%je)&p1A5m)6VS#h%je)&p193!
!6Y"19J!!,&p)jq$J-#m!(#"[!"j$l`!L0!$P5Y,#3N&5L'!3*'&#3K3DdN*J!K$
D8FVrr&()rqiJE`!H%)&$l`!L-#m!(19)dX!M,`!B,dN!'%cI"`FZAdje6Pj1G59
I3d&8)*!$6PB!!#aI51IJ`#"[!"J`,`!FEd!-3!$rEMSb,`!HEc3-33$rELj635*
[!#"#3K3CP%'83'dHdX%3`'!#%0P4b2rm,fm!&!!J60m$"prm!*!$$%je3K"JkNj
H6R8PAd028&NJ)%j@!!!XAb)I-"mLAb"I,`&)ja!J8d"Y+N*"%KK#3K34Y%"Y(MB
"eN)5`b4*e-25`T4!B!)9)9(+rraJ!K,B8FRrr%cI"!K1G8jH6R8PAdP18b#3!dj
@!!!XAbm!,`%`,`!3`Hm!$M)[!!c$l`!5d%&)3%*!-Lm!%X,[!!l3J5p!!"!L(b!
I,eF!"&L26R91ANje*8PI699-0#"19J!!,`![!5![!"3L,`!36VS!1Lp"!"3L(b!
I6Pi[9`!%@)p1G8j@!!![!#m")#m!&#)[!""1ZJ!8,d!!&#)I)"p1ALpA!!4BMdj
e6PB!!#aI51Fq!#S!DJ*%J#`"DJ*%J53"5%*+3QBF0J"#3%K!C`5!`63!5%)`!i$
"0!!L!N*!5%"J(#3!*J&#J%+"H"r8JY'!dS'`Jfd%N!#$8J&4c2r`5S9U!N5!ZiC
U!N5"60m!I%je6Pj1G8P%59C06d3J6PB!!#aI)(Vp[%l36Pj1G59I5%&-9#!J6PB
!!#m!F!!3,J!+X'i!#'m#qYiJ(djH,Tp1G90dFP*R3fKV51I!`()"B!C)jm$!3N%
JE`!B)Qm!&%*!%"L`'@B1B!5c#'B)8d"Uq!T"!!%I33!D,fm!%!!@60m$!eb26R@
Hr!%!51I!`$![!43b,`%@3Hm"'#*[!4#53'`)3Q"83@[kB!,3`5%*,dJ!%%cI!`-
ZAdje6PB!!%+YrUj#,IkS3LhqU8+YrU*#VIkH3UhqNNjH6R@P58j*9%K&33!!F!"
J!!#+F!&J!!#%F!*JIR!$B(T`"'"fF!9JFR!'B'j`"f"UF!KJCR!*B'*`#Q"HF!Y
J@R!-B&C`$@"5F!jJ6R!2B%T`%'"'F"&J3R!5B$j`%f!kF"4J0R!9B$*`&Q!ZF"G
J+R!BB#C`'@!LF"TJ(R!EB"T`('!@F"eJ%R!HB!j`(f!+F#"J"R!KB!*`)Km!-(J
!i,$i!UjN!!!1)(J!i#"3)&"+N!"Q*&92,caKG("XUC``(fFL@8m[2'&dF'`r!+Q
G)"pR$#"!(c`!)bm33KG1G6!i#Q"Q"$!mrd"d!()!&"mLAd(k!"S5-#!!hX%-3J!
LC`S-3J!"EJ*#3$k!6Y%!N!3'!JB'"!J##!J%!!!)!JD3"33'N!31"J3!!!J!!%j
@rrT)j`%)+'h`&L!-9X"R%L)X!!DbVJ!)9X(!!@F%+&4Jk#!-Cb4#Cbm-3Hh`&#m
)6VVj-$iI,bi!#+Q$,`a1Z[L#(A`!!3!-B!4#,J!-60m3J%jH,Tp1G8j@rqSJEJ!
)3qlrm#,B)YJLf#,B3QF[,[rk5'lrl+NX29rrkL!Zrqb`VI%HCJi[,I%HU4BGI!!
"!!aJ$N*R,blrl%kkrf)GA`!-6PiZRdje6PErh#m-)'i!#%2Zrr!Lf#,B)YJLf#K
Zrr+jlI%HCJJGI!!"!!aJ,%+R-#lrrNM!,`"`!5m!U&J-R`#3!`&Q#"em!!%!$'!
-3QF[$%kkr`JGA`!-+&p1ALkI6R919J!!,bha(UN9,bha(UNI6Pj1G8j@rr)[,I%
H2c`!!8KZrrj)E[rk5'lrmUQ0,blrqLmZ!!bTMbmYm4ir2!!#5'lrrNKZrrT)E[r
bUBd[,[rk,bi!#+Q26VVrTNjH)&p36dl36PErmLmYm4ir2!!"5'lrrNKZrrT)E[r
bUBd[,[rk5(S!0+Q2,bha(Mmm!!*)E[rq5'lrqNKZrr+TM5mZrrS[,J!)UBmr2!!
#UFK1Z[p36PiZRdje'8%JEQ9dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[lb,bha(Mm
m!!&)E[rq5'lrqNKZrr+TM5mZrrT)HJ"+UBm[,I%H2c`!!NKZrrj)E[rk5'lrmUQ
0,blrqLmZ!!`[,J!)5'lqmMmm!!*1Z[Qk5'lqmUQ22c`!!UR)6VVqcNjH)&p36dl
3'8%JEQ9dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[lb,bha(Mmm!!&)E[rq5'lrqNK
Zrr+TM5mZrrT)HJ"3UBm[,I%H2c`!!NKZrrj)E[rk5'lrmUQ0,blrqLmZ!"![,J!
-,bi!#%KZr[)r2!!$6VVj-NKZr[+TMcmm!!+Tb%kkrNC1AL"Ih[`!$%l3'8%JEQ9
dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[m!-#i!#!4!rm0V!!(3$%!!('i!!FMM5$!
l!!C1q`!!!CS"[J$k!+B"[J'q!Ei"[J'q!Ei"[J'q!8`!8J#k!BB!d!'X!13"FJ%
N!@!!NJ'q!$`"%!%i!(`!D%(Zr`"$qJ0-F!BJf90!E[S`N@!!!@j"l[m!3rS$((!
')0P63'lk-*&J!!&B3Hlr!%2k!Zj`"L$C8d"ZqQ!!!84"l[m!3rS#b(!%)0P63'l
k-*&J!!%Z3Hlr!%2k!U3Jf5$C)0N`N@!!!4T"l[m!3rS#G(!()0P63'lkB!!""N(
Zr`"$qJ*1F!3Jf90!E[S`N@!!!2""l[m!3rS#*(!&)0P63'lkB!!!h%(Zr`"$qJ(
fF!BJf90!E[S`N@!!!-C"l[m!3rS"aR!')0P63'lk-*&J!!#`3Hlr!%2k!D!Jf5$
C)0NJf@!!!*a"l[m!3rS"I#$C)0NJf5$CB!!!L%(Zr`"$qJ&1F!BJf90!E[S`N@"
b3Hlr!%2k!44`#5$C8d"ZqM#4B&j"l[m!3rS!h(!*)0P63'lkB%a"l[m!3rS!V(!
()0P63'lk-*&J1%(Zr`"$qJ##F!8Jf90!E[S`N@!N3Hlr!%2k!%T`#5$C8d"ZqQ!
53Hlr!%2k!#*`"5$C8d"ZqM#4,bi!#N(Zr`![#%kkr3K1AL"IA%p1d"9)BA*NGf&
bC5"fEfaeE@8JE'pMDbiL8'9bE@PcFfP[EL"NEf9c)'j[G#"KE'a[Gb"hFQPdD@j
R,J!98fpQG(GKFQ8JGQpXG@eP)'a[BfXZ(&4[Eb"YB@jj)'CTE'9c)'&bC5"[F'9
Z)'j[Gbi!)e4SC5"RDACPEL"QD@aP)("[FfPdD@pZ)'Pc)'PZGQ&XD@3Z*94SC5"
QD@aP)'Pc)'&XFQ9KC(NJEh"PEL"QEh)JGh*TG'PZCbiB9'KPFQ8JDA-JEQmJFh9
MD#"fEfaeE@8Z!!p0C@e[FRNJDA-JCR9XE#i24'PcDb"*,dmJCA*bEh)Z'84TCQC
TBh9XG(NJGfPdD#"bC@jKE@PZCbiB9'KP)'CTE'8JC'pPFb"ZEh3JCAKTFh3Z!"0
8D'8JCQPXC5"TFb"XEf0VC@3Z%94SC5"QD@aP)'Pc)'*eFhNZ'd9iG'9bEQ&X)'C
TE'8JFhPcG'9Y)'9bFQpb,Ja&EQ3JEfBJCQPXC5i!%94SC5"NDA0V)'Pc)'CeE'`
Z&P4SC5"NDA*PBh4[FRNJDA-JCR9XE#i!'&4SC5"QD@aP)'&XFQ9KC(NJCAKTFh4
c,J!C9'KP)'CTE'8JEQ&YC5"TFb"TERCKE'PN,Nj@!!![,J!-3UG)HJ!@6VT$Y#m
Z!!K1Z[Z@6PiJAe"26Y!81L"*)'0KELGd)'GPG#"K)'jPGb!!6PB!!#mZ!!a#TdK
k!"C1ZN0m,bi!#%kkqej1AL"I8%p1d#Bk)%NJBf&Z*h3JEh"PEL"K)'0[EQjPBh4
TEfiX)("bEh4[BfpX)!"19J!!,bi!#%+R5(S!1Nkk3c*#TdKk!""1ZN-S6VVl$Nj
H,Tp1G4j8D'8JEh"PFQ&dD@pZ)(GTE'`JBQ8JB@*[FR4PC#i!*cSJ9'KP)'C[FQ9
TCfiJD'pcG#"TFb"ZEh3JFQ9cF'pZC'PZCbiJ)%j@r`")HJ"#,bi!#NKk!$4)E[m
!2c`!!dkkp#T"lI!H3qlr!("!)0P63'lk3Hh`(Lm)2bi!#%kkqa"1AL"IA%p1d!3
L1b!J!!j*)'0KELGd)'p`C@iJ)J"19[m!5(S!3LmZ!!T)HJ!d5'lr!$mm!!01Z[2
53Hh`(N2Zr`"`3#$C8d"ZqN(Ym"i[#$mZ!!K1Z[Ui6PiJAea26Y!%)MXJ)!!555"
MB@iRG#"hFQPdC5"dEb!L!%j@r`")HJ"#,bi!#NKk!$4)E[m!2c`!!dkkmhC"lI!
H3qlr!("!)0P63'lk3Hh`(Lm)2bi!#%kkqPa1AL"IA%p1d!3L1b!J!"0*)'0KELG
d)(*PB@3JCR*[E5!L6PErrLmZ!!T)HJ!b5(S!,NKk!#UTLd*R2c`"68+RUBBpArr
q%#i!#'F'6VT"Z'!%6VVe*%jH)&pF6dl3!!"19[rq,`Gq!@!1)!G"lI,Uk8"#-!!
18NF-4`!3Eq`Z(djH6R919[rd,`FJEJ!)3qlrpL,B)YJbN!"q!3a(!""I`'i5)JG
"lI,Uk8(!-"!1C`454f$Q$%F!%'ib)!G"lI,Uk8!KVJ!-!!!J"d(YmZVT3%(`!!4
$l[rf)0NJf6#4)!G"lI,Uk8!4[!!"!!iZ(djH)&p36dl36PErrLm(IJ&J1L!(3Hh
bkZP!%M!!$QFU)!G"lI,Uk8!L-!!!XUi!#'BB3UFJ"d(YmZVT3%K`!!41ZN#f,9m
!$'!-8NF-4`!3Em"#VJ!-,Kp1ALkI6R919[rq,`Gq!@!`)!G"lI,Uk8!5-!!1Cb!
J"d(YmZVT3#)`!!#bVJ!)CJiJ"d(YmZVT3%)`!!jJ#&*($%F!%'r+,Kp1ALkI6R9
19J!!3Hhbh,(YmraR+%+R6VS$1#!IX+hcr&r!)'hcr#)YmrbbU!!+9X'!!@F),bh
cr%kk!M*1ANje6PB!!#"Z!!J4I!!"!!j1ALkI6R919J!!,bhcr%kkrq*1ZJ$+6Pj
1G8j@rqK)j`-B)'i!$%2ZrrBLf#,B-T!!-#i!%0"m!"!q!#!(!N!!!@F#8NFQEI,
X3SDCc#!,9X!L$&I"`!&R+VjV!!KZ(#K,2LX!#%U'CJJVD`!%mZaJ%#"')@X!"!!
%B!BX#bCV!!4JbL!-CJT#Tcm(6VS#C#KI,`a)abm(,bi!%LmZ!!K1ZJ(d'A`!!3!
1)'i!&LPS!!3!"#"Z!"BK6!!%,`a)E[rf6VVpjMP(!!JT6!!+,8`!'NcI'-"1AL"
Ih[`!%Nl36PB!!%kkrY3VEI2mm[!JEI,`+fJ!"2,`)'hbm#!Ym[#`U!!+C`J[,I2
m6VS"&#"Ym["++!!1CpBJEI,`3LJ!$L!Ym[#`VI2mC``[,I2m,bhbm%kk!6`VEI,
`mr`3,I,jCa4#,I,j)'hbp#&YmZ`!"#YYm[6bl%jH6R919[rm,``SEI2m)#`!",#
YmraR"LKX!!4Jm#"Ymr`TD!!%!!3EI!!"m[NVEI2mm[3[,I2m6VVpiNkkrda#TdK
k!"*1ZMjZ3QG1Z[bN+&p1ANje)%4TFf&cG'9b1L"dDepPH'Pd+#NJFQ9dGA*ZD@j
R)C!$!%j@r`"#TdKk!%4#TbmYmra1Z[dm5(S!)%KZr`!r2!!$6VV[M%KZr`"1ZMi
83QG1Z[a+6Pj1G43JDA-JG(*jD@jR)(4[)(*PG(9bEJ!&9'&cDb"19[hq3UG1ZJ$
F5'lprNkkla)[,I2m5'lqrNkkl`C)E[hq5'lqrN+R,bi!#%kkr-j)HJ!FUBY#Ccm
m!TT#TkQ'29rrrNkk2FC1ALkI6R8!!%(k!#!JRb*251FI2L"T!!3JMb"4,P"-hhc
i8)mLHJ!%6Y%!N!4"qJ"!))iJE`!3dHm!$#%[!!4$q[mN)3NK,`!))3NX5*(m!*!
$&#%*)3P)i"mq)Qm!%#+))&rIr!#3!a!XHJ!%6Y!!N!3J$b"I3S%b(j!!J82k!!B
LJ%l3!*!%)&p#J$!I3rVrp*'4,VVrlNl3)&mL6bk*6Y"19[rq51F"##KZ!!K#Cbm
-6VVVHMiI3T3jEJ!@!!T#E!!-)!ab%Y#"+8!!"MPm,`d!%MPm3IJ!&$Pm#33!&MP
m+P!!'$Pm,c`!'LPZ!!`!($Pm6VN!)#PZ!"!!)MPm+Pm!*MPm6R8!+%*R,`a1ZZX
32Kp-ha#!6PiJAplm!""1d%j@!!![,J!8F$`[!%kkl[i[,J!3,bi!$#mZ!!K1Z[p
L6PiJAplm!""1d%j@!!!JEJ!)5QJ!#QB'3Li!$'!D3QF[,J!)6VVUaNTIC`C#,J!
-B!BGI!!"!!a1ALkI6R919[rf51F$##KYrVJJ$'F53QF[$%(YrVB[#%kkk[Jm(f!
@ILT#Tbm(6VVU2LKI)!aQ"N+Z!!KJ'N+81A`!!3!%3Q`!#LPYrV)!$LY-rV)Y6!!
)60m3`%jH6R919[rf51F('#CZ!!K#Cbm,6VVU3NTICK"#Cbm,6VVU+$SI3Li!$'"
QYqhqXQB)+fX!$[kbB#JSEIkbIJ&+V!!19X!L"m)!CaDhl!!1CJSTD`!1!!j#"f!
%+'`!$Q$H3NBSEIki)!aR"P*'+&4JpJa'!"jX$Lm,3HhqYLm)6VVU,'!',`Y1ZZQ
8(A`!!3!-60mBi%jH,Tp1G8j@rrT)j`%)+'hqXL!-Ca"#Cbm-6VVTTMiI+'`!$Q$
X3UhqXNcI%)"1ANje6PErl#m-3UFr2!!I3UG`rbm!UA`SAbm-2c`!!dKZrrK)E[r
d5'lrl+Q0,blrp%KYmG5TMbm-2c`!!d*R2c`qJ+Pq3UG)E[rqUC%-EJ!"rrjQ#Lm
Zrr3[,J!)UC!!,`bTJ`aZ!!(rrPI!4!!G3!!-+&p1ALkI6R919[rm,`F3,Hle5)!
p32rm3NGJ+L!(3HhZpX(m!!SL-!!%XUi!$'B5)!G"lHlf`I`!#L'Z!!J!!'"H8NG
T"VjZrra[d"!Yl[4)J%(Yl[E"r!!+)Di!$!!%%#hZp%L!3HhZpX(m!!SKVJ!)!!!
3,Hld5)"53"Y!l[3-,3!+l[4["%)Yl[3-,3!+l[9R$"!Yl[9)J&*!'d$Zp5iI6Pi
JAe"26Y"19[rk51F"##KYlf3i[!!$1A`)!!!#F!3C3!!%F!3C3!!&1@i!%!!'3QG
)E[rk5'lrr%kklUT+AfF#B(SEE[rllfilE[rmlfa`5"Y!lfmTEHpX!!JTEI,8!!`
TEJ!-!"!TEJ!)!"3JEI'Q)&!-D!!"!!*Q"NkkqA*Jl#"YmDBS8$Pm!"F!#"!Ylfm
#3!$r18!!#LPZ!!`!$$Pm!"J!%#PYlf3!&%*R,bhaTN*R(c`!!8kklG3q(dcI%)"
1AL"Ih[`!#Nl36PB!!#m-6VS#c%kkqI!SEHpd$&3!!fF'8QhZjQ$S$'`)!!!#C`C
5EHlQB0SJ,!!8X+hbe'F'8QhZl'$+,b`!$#mX!!K1Z[j8-#`!"P0!C`C63'FBB$"
5EHl`2c`!!LmX!!J[,!!-6VVqe'!H5Uh[F'F+,bh[F%kkq*KJ"&*YlZK#VHp`B!4
5EHlUB!$rHLKI6PiZRdje6PErr#m(%#hZp8L!28$rr%*(B$)J"d(Yl[E"r!!+)M!
!",+Z!!aQ'L!(3HhZpX(m!!SLEJ!))V!!!"em!!%!%'!18NGT"VjZrra[b%)Z!"!
Z(djH)&p36dl36PB!!%UZ!!K@`'F@)Li!#,+Ymra@`F!"C`J[,J!)6VVi!%jH,Tp
1G8j@rrT)j`-),#i!$%*R,`B[,J!)6VVrD"!IC`4J!!$@+fhcr1p`2c`!!5mYlfJ
["NkkrI*#TdkkqlBSAb!-CL)JEJ!)3P!JEJ!)F!!43!!$)'i!#(!!%8!!!N+Ylh"
J!!#8,c`!!!1%3IVrELm),bh[F#m-6VVkQNkkq'T+VHp`CNC#Cbm-6VVl-KiI3QF
["LmZ!!K1Z[lS%"pR$N*R,`a1Z[ZQ(KpJ6'"),c`!!!1%3IVr*Lm),bh[F#m-6VV
k8LYYmrc[F'!U3QF[$%kkqhSH(b"Z!!K#8#"Z!!K`!"&!!!-JEJ!)F!!43!!#3Uh
[F'!#B)4-ha$!6PiJAe"26Y!-!!")C`4J!!#Q4qS!!3`V!!%!!PD,CJB3+`!%B!3
3+`!-$!!!&QC54rS!c%U6CK*#3dkX!!*1ZJ$f3IS!Z&*36R8UHJ#f1d%!$MBm!PJ
QHJ#Q6U`!!N(k!*j#N!""qJ#F,a"1ZJ$+4ZS!(#"I3rJ"DL&4!!T1ZJ#N6R8-!!!
AC`*J,%Ik!%*+%fB#B#)f2!!B4rS!i%kX!!*+JfF#6R9"qJ!Q3K"'kJ!F6VS!C%j
e3N01V!!#3IS!2P*36R9"qJ!)%,`!!8je!!""qJ!i)+m!%%(k!$JJV`!-3IS!*##
[!!K"qJ!N)+m!"%(k!!T#8%kk!$j1G3#3'N(krrS[%%kkpH*1G82krq)L8DP[3IV
rhLm36VVecNje,`a"q[r8)P"+U3!#Ca`JD3!#+%LTENT!CK""q[q`))a"q[qQ)+`
!"Q!-3IVrS%+3!%(krjC#N!!SAdje3IS!##p)!!41G3#3'%j@rpa)j`mB*Qi!$N+
R,`Y1ZJ6f,"p#Tbm'6VS&!#KI1#`!!N*X!!)3,J!0!N!!!@F3,8crhL"Zrpi`,J!
-3M!!!%+R3QF[$%+!-Li!$&*"-!(LL$m!6VSe0$!I5-![!+KD)"mp32rZZ'lrlQF
1183!!Lm,6VS&P'!!!B)j4!!#%"3#3!$r9d"R%&9!C`!!P&&!C`!"#'!!!9j#Td+
R,`Y1ZJ4Q6VS%GLeIrr"#4f!Z)!G"lHq8jd!LE[r`)M!!!,+T!"KQ&L!(3Hh[P1G
!3V!!"#m,6VS&0'!!!5*54`a(!!p[c#"Zrr!`,Hq53qh[P1G!)kJ!'!!!-#h[NN(
Ylj6R3%+`!!3-E3!2lj*Q"N*Ylj*J"&*Ylj)[#dkk"1jJ!!$F+Ja#4f!`)!G"lHq
8jd!L45)`!!#bU3!BCKSJ45!(3qh[P1G!)kJ!"!!%,`Y1ZJ5kB!!!U&*($%F!$fr
+)%8`,Hq53qh[P1G!)kJ!'!!!)%8`,Hq53qh[P1G!)kJ!"!!%$'d!$qq5CJC#EHq
5B!45EHq5,`Y1ZJ4bB'"`$KL!3Q`!!N+R3QF[$%+!-$`!&1+)2`"1ZM2@-"p)`#m
!U&SJ(cP!!!)J4L*')fJ!%!!-)%BKEJ!)!""#CbmYlii[#cmm!"3[,J!)6VS%kNT
I,`Y1ZJ3BB!B[#dkk"""-haM`6PiJAplm!!T1d%j@rqj)j`FB3Qi!%N+R2c`#!%*
R6VS$(LCI)!YQ)%+R5(S!Z%kk-eC#TdKk!+C1ZM0-6VV[K%*Z!"*J!!#)3UG#Tbm
,6VS#Z%kk!XJSAh!$')!CEJ!*!!&#4f!H,#i!#L!-8)!U!#"&)!FL4K)aF!!#33$
r%B%!!&*($%F!'frF3Q`!!N+R3QF[$%+!-$`!*1+)2`"1ZM,X-"p)`#m!U&SJ(cP
!!!*#CbmYlii[#cmm!#3[,J!16VS%%MiI5NF[#dkk!cj-haMJ6PiJAplm!!T1d!C
`B@0VCA3!#dP$69"I4%969&916PErq#eZ!!crq#eZ!!Mrr%+R%#lrq!*!!2p)`#m
!,c`!N!1!U&K+RfBd%#lrq!*!!2m5,[rm!N%!rl*!9m!5,[rj!N%!ra3Zrrd#3J$
rY%&A`F!"4!!G3!!3B!!!dN+R%#lrq!*!!2p)`#m!,c`!N!2!U&J-R`#3!i"Q5"!
ZrrJ#3!$r%Llrr!*"!2qb3&I!%Llrq3*"!2m8,[rp!N)!rl4"9m(!!4)ZrrS#33$
r&#lrrJ*#!2qd39I"`!&%!"e!!""JDN+R%#lrq!*!!2p)`#m!,c`!N!2JU&J-R`#
3!m"Q5"!ZrrJ#3!$r%Llrr!*"!2qb3&I!%Llrq3*"!2m8,[rp!N)!rl4"9m(!!4)
ZrrS#33$r&#lrrJ*#!2qd39I"`!&%!"e!!""J"%)Z!""1AL"I8%p1d%j@rrj)j`-
)+'i!##`Z!!a#4f"+)!G"lHq8jd"+X!!!CJ4J1'!f)!G"lHq8jd#mX!!!CLJJ"d(
Ylj6R3#L`!!3J"d(Ylj6R3%U`!!4Q$#!(3Hh[P1G!3V!!!'!J8NF-4`!2El"#Cbm
YmY3["NkkrP`3(fF%+)CJ"#LYmYK-ha$!6PiJAe"26Y"19[ri51F"'#iZ!!j#Tbm
(6VS!5#CI3UF[#dkk!&)SAa!8!N!!r`a!!!KQ'R!!')"#CbmYlh`["cmZ!!`[,J!
)6VS"j%TI,`G1ZJ%560mBJ%jH)&rHr!!+6Y"19J!!)'i!##eS!!B!$%jH,Tp1G8j
@!!"#J#"Z!!J5%!*"!!m`!H@)d+i!##e!!!a1ALkI6R919[rd51F('$`Z!!K#Tb!
'9N")`#m!3UG`!bm!U&US@#!I2!!J"Y"m!"3b,J!+dN"536i")!F#3!!"C`*64`a
Z!N!!#Qm'3Ui!$'"N+'haV#!-CaK#CbmYmDa"lI'U,`K1ZYlB1Kp+4@F#QF`J$'B
H3UG)HJ"16VS[d%+R5(S!2%kk,mC1ZZ[q3Ui!$'!L3UF[$%kkrcBQAb!'5-#"r!!
%@N!#3!!2!K2rm)%6,8`!$%cI'1"1ALkI6R8'F'&MDf9d!!K*6Pp"6%a23`"19J!
!,bi!#%(YmDS[#%kkhNj1ALkI6R919J!!5UhabPE!4!!G3!!)6Pj1G8j@!!!YEI,
8!!a1ALkI6R919[ri51F"#$!Ym9j63$e!rrK#4f!H)!G"lI&Jj8!JF!!!-"#`EJ!
-CJC#VJ!1B&*54fN'[Qlrq'rF$'d!#[&HCJ5Cc'!5-#haAN(Ym5,"r!!'3I!!!#K
))!aQ"N+Z!!jJ)$LZ!!`TEJ!)!!)`,I&H3HhaB19!)B`!!&*Ym9iY6!!160m3J%j
H)&pF6dl36PErm%MR!aJ-EJ*B!!a[#Memrrm!&Q!!!6)[,J!)3Hlrr#m)6VVp,%U
ZrraQ#%*Z!"CJ!!%@3UF[,J!16VVppLCI!P-2r`"63!!`2!$r&d!!#!*V(rm!"J*
Vi!!!"MGYmBJ!"&*YmBK#D`!+*fhbe!!-*fi!#!!33S!5%`*"!!m`!H@)-Li!$%M
"dS!q!6G(!!*`!"G!!!%JEJ!5&fJ!!3!*3UG#Cbm,3S!5%`*"!!m`!H1)2`"1ZLi
!-"p)`#m!U&SJ(cG!!!T5EI'+,blrr%KZrr*1Z[A-%#lrp!*!!2p+3'B)2AcrrJ!
@B&iJEI'Q)&!-D!!"!!*Q"NkklE*Jl#"YmDBS8$Pm!"B!#$Pm!%J!#LPZrr)!$$P
(!"!JEJ!1+@J!"J!83QF[,I'Q3QFI2!!"6VVL'$`I5NCQ#$em!!%!&Q!'2Acrr3!
@60mB`%jH)&rHr!!16Y"19[rk51F"#%+R,bi!#%kkr-`SAb"Z!!J`+!!1X'`!!Q`
-8QhaR%)Z!!aJ!!#B%"6S5!*!!!m-3!!%C`T5EI'83Li!$'"q2L`!#N*X!!T)ad+
R3QF[$%+!%K3#33!2-!(ML$m!6VSXqM!I5-![!+KD[TpR$MP(!!T5EI'H3Li!$'"
#)#`!%,#YmY4R"N)Z!!aJ-M!X!!B#3"rr5N"@`")X!!EU53*"!!F#33!"J!%#3!!
"C`T5EI'3!%)Z!!aJ"Kem!!%!$%cI%)"1ALkI6R919[rU51F2'%kkl$"+VI(+CJ4
1ZZe-+'habL!-CJC5EI'1B1K#Cbm-3Hhab#m)6VVE6NTIC`*Je&*YmC*#Cbm-6VV
qm"!I#J!!!@F-8QhaS#m-6VVm['#d3UF[$%kkql!QAcJV!!*#"6!Ym9j63$e!rqT
#4f"1)!G"lI&Jj8!X-!!!)%B3+`!*!N!!rl"3CM!J4NUS!!*Q"'!`B#3[$#!%N!"
m!"3r!#mV!!`J4LmS!!*1ZL[56VVVK(S"6VVVc'!+8NGT"VjZrqT[V#!&#J!!!@F
L3QF[+`!-,`Xr2!!#6VVi-$eIrr45EI'@8QhaS#m-6VVm(Q!!raC-haM`6PiZRdj
e6PErrN*R2c`!5%kki"3pArrq6Pj1G8j@q[`YEJ!-rra#Ta!Zrr`#3!$r5-![!#m
m!*!$rkKB5'llr%kkh)4#Ta!Zrrd#3!$r5-![!#mm!*!$rkKB5'lmr%kkh'C#Ta!
Zrri#3!$r5-![!#mm!*!$rkKB5'lpr%kkh%K#Ta!Zrrm#3!$r5-![!#mm!*!$rkK
B5'lqr%kkh#SJEJ!),`K)E[[m5(S!1%KZr2a)HJ!`5'lpr%Kk!#K)E[lm5'lkr$m
m!!G1ZY`f)&p$l[VmF%!Jf90!E[T1AL"I8%p1d!%Z6PErr#m(3UF[,J!)6VS!+#i
I5SGQ$%+R,bi!#%kY!b)Z(d(Y!b)[#+Ra,8F!$#iI6PiZRdje6PErpNMR$`!JEJ!
)%"!#3!$r2J"+4fB)3Ui!$'!!!-3JEJ!)F!%5-!!!!N%!rfXBXR`!2fi53IS!a$!
"jNK%3!-`!2m+2!!%CPK#"!a(!!KX"$S(B!*k#$e&rrCm!Q!B)'i!#"!`B!!#3!$
r$%!!,QB#H!&54QN'['lrpQrL%!4R%%+R,bi!#%kY!cSYA`!-B&"#TbmZ!!K1V3-
U,9m!$'"!)'i!#(!"%M!!!!*"!2m-33!M9m!JEJ!)FJ%8-"!!!N)!r`a#!#4A`B!
"Ca"#TbmZ!!K1V3-b,9m!$'!%3Ui!$%(Y!c)[#+Ra60m!m%jH,Tp1G32r!*!'6PB
!!$!Z!!U`EJ!)AF"%!"e!!!a1ALkI6R919[rf51F(#%+R,c`!!!LS6VVAP#KI)!a
Q)%+R5(S#CNkk+6j#TdKk!Q"1ZLNd6VVPE%+Z!!KJ!!)m3T4#,!!%,``JEI3%,a"
1ZYIi3UFr2!*!3QG1Z[M#+9m!(%UX!"aQ0N+R5(S#(Nkk+2C#TdKk!Ja1ZLMX6VV
P*%*R,``JEI3%,a"1ZYI-2Km[$%kkeb4#VJ!)B!!"hN+R3UF[,!!F6VVi3%kkq&!
TA`!J)#`!)()8d)%T3!!N3UG`$#m!6VV@jLPI!#K+V!!SCKj#Cbm-)'hd"#m36VV
AHMiI,`a1ZYE53Ui!#'!!!B`JE!!SF!!43!!))'`!+(!'%8!!#5"Yp!4#D!!H3Q`
!"R!mj8!j3!!f!QcIr`LQ3U`!#%*X!$K#E!!q!Qbrr`LQ3U`!,N*X!$)#E(rr#+B
#E2lr#+C#,!LR)'`!)%+S!!3JE!!J3UJ!##"X!#!#D2$r!!`JE!!J!QMr2`!-)'`
!)!*S$rm!$#"X!#!#D2rI!!`JE!!J!QMrl`!-)'`!)!*SrrF!$#"X!#!#D2rl!!`
JE!!J!QMrrJ!-)'`!)%(S!!`#82rp!&!!!L"X!#"#D!!5)'hd"%*S!#BjI2rr!$S
jI2rr!$a#Tdkkkf`TA`!85U`!&'BF3QF[$#"Yp!3[%%kkeQJq(bm-6VV9`%+Z!!K
JHN+R6VVV3#PI!"K+V!!BCLK#CbmX!"41ZZZ''Kp#Cbm-)'hd"#m36VV@-$iI,`a
1ZY@)3Ui!#'"#3Q`)3%*X#%*#E!K%3Q`)4N*'B#3J"R)"!N%!!63!!N)!"qC)3q`
)L%(a!!"`rZ8ij5R"%)-38NB-4J!2EpBY6!!)60m3i%jH6R8'F'&MDf9d!!083e!
+BfpZEQ9MG'P[EJ"19[rm51F$##KZ!!K#Cbm8)'hd"#m36VV9TM`I)&3[+!!F6VV
h,%*R)&3[+!!86VVU4KiI3QFJ9#mS!"41ZZV'(Kp#Cb"8,bJ!'%kkkLSH(d*R)&3
[+!!B6VVUUKiI)&3[+!!S6VV8Z#m86VV8XN+860m3`%jH,Tp1G8j@!!![$%UZ!!K
Q"'!!!+3SEJ!)-#`!"QF88d"RC&0!C`a63'FDAd"R"'!!!)B[,!L@6VSQ&NKZ!!K
1Z[p+B()jI!!%!!BJE!!J3HJ!$!*3rri!8!!")'`!)!*SrrF!$%(X#+B#8(rr!&#
!!%(X#+B#80rr!&!J!#mZ!!K1ZJk)B$!JE!!J3HJ!$!*3rrX!8!!%3H`)TJ*3Irm
!8)!!3H`)TJ*3hrm!8#!!,bi!#%kk$PBSAdjH,Tp1G8j@rqK)j`mB+Li!#%UZ!""
Q"'!!!2)SEI3%3UG#TbmZ!!a1Z[6f6VVe"LCI2LX!!MG6!!)fKb`V!!3RD`!)!!3
R4J!)!Q[rh`!-!Q[rl`!-!Q[rp`!-!Q[rrJ!-!Q[rr3!-F"4)`)(m!!4"k`!-!N!
!$qP)!K$r$i%33HX!$!*3rrX!8!!%3QX!$N+R,`91Z[@i,9rrkLe&rqj`!"e!rr*
`"Ke!rr-pI!!8rr4#Cd(ZrqS[#(!-5-#"r!!#2`"1ZL6H0em!%%+R3QF[#h!85-#
"r!!#2`"1ZL6'-"p)`#m!U&SJ(cG!!""#CbmX!!B[,J!-2c`!&#m&6VVel$JI5'i
!%%kkrFK-haM`6PiJAplm!!a1d%j@!!"#TdKk!"41ZL4i,bi!#%kkfpC1ALkI6R8
(3fa[Ff9N1Nj@!!"+EJ!)CaK6EJ!))'i!$L*Z!!S5N!"5VJ!+8Ui!$Q$L6PiJApl
m!!T1d%j@rla)j`mB,#i!$Nkkip3JEI3%,8Mr`N+R3UF["NkkmjC1Z[1Q*Pm3+`!
-k%J#3!!2j8")`0#,,8$rq"!V!!cS5!*!!!rP3$)Z!!b53$e"rqiJE[r#-#J!&P*
!)'lr`M&!!"C+E[rZE43`,[rZ5-$3V[ri,8$rjL"ZrqC#%(!!(8$rd(!'(8$rd4!
V!!cS5!*!!!rP30"Zrqip32r53UF["Nkkma)YArrD3UFJE[rD,bJ!$%kkp#iYArr
-)'lrfLeS!!crb$eV!"$rj%*R3Hlrb#m)F!a)`)(m!!)r!%kk)eShA`!33UG#Cbm
,-#lrlP*!5-#"r!!#%LX!$1K*!N%!$q0"dN!r!8kk)c!`(dM!,`#S@L!I0d!!%$!
V!"#`E[rNCa`JE[r#-#J!*P*!)'lr`M&!!#B["NkkmhjJ!!M+)'lr`L"3,@J!![r
H(A`!!Ir95UlrhPE!`#lre@F!!**#Cb"Zrpi[+!L56VSL`"!ICaBJE[r#%A`!!3!
%6VVLXL"Zrm*#+!!%)'lrhM!V!!+`D!!-CNiJE[rH5QJ!"PI!Cb!JE[rH$'J!#J!
'9X&R,L"Zrpid%l4S!!j@`X)#J!&R(#"Zrm)`+!!H8N!JE[r#-8!!(Lm'6VVbi'!
!##a#,[r9B!JJE[rH,9$rhQ!!rf33,[r9Ca`JE[r#-#J!(P*!)'lr`M&!!"i["Nk
kmUaJ!!Ii+'lrhJJV!!)!$@Fk$'`!#J!'9X"+E!!'9X(!!@FH3UG)HJJ36VSKr%k
krA3[,!L@6VSKk%KZrpj1Z[XF,`C1Z[*QB!!(XJaX!!%!"QB!!0))+`!%!!eA`!b
V!*!$!3!)9X'!!@F+,`C1Z[)mB!!(L!JV!!%!$@B+,`C1Z[)UB!!(GL"X!#!#D2r
p!!`JE!!J!QMrq`!-)'`!)%(S!!`#82r[!&!!%#"X!#""k!!-!P$rp`"3!!JJE!!
JF!%K3!!%)#X!"&+!*d!!"#"X!#!KD`!%!!JJE[r#-A`!!3!D1@X!$J!X1A`!!`!
'F$cM3$P!!$C"l!LQ!P$Ir`"3)!"#CbmX!"41ZZ6'(9rribmX#)T1ZL%),blrhNk
k#EaJ!!'N$'`!#J!'CJ!!X!JV!!%!$9I!##X!"!!09X'!!@FB,blrhLm',bi!#%k
kqd!["Nkkm9CJ!!DL1A`!!J!'+@i!#!!)19-!$L"X!#J[#%+R,b`!#%kkm9iJ(b"
I))!JE!!S)@`!#!!%)'`!)$#X!!`JE!!J-@`!$J!#)#X!"&+!)'`!)#&!!!JJE!!
J3HJ!$!*3rqm!8!!33H`)TJ*3hrm!8#!!,blrhNkk#3i["Nkkm0KJ!!BNB!!!l!a
X!!)!"QB!!1)JE!!J)#X!",#S!!KR'#mZrpi["LmZ!!K1Z[U1,`C1Z[#NB!!&m#"
X!#!)+!!%!!eQ#Lm'6VV`MQ!!"GSJE!!J)#J!"&+!X+X!#'FB,blrhLm',bi!#%k
kqP!["Nkkm'CJ!!@b)'`!)!*Srrd!$#"X!#!#D2rl!!`JE!!J3HJ!$!*3rqm!8!!
3)'`!)%(S!!`#82rh!&!!##"X!#!KD`!%!!3JE!!J)@X!#!!))'lr`M&m!!%!'MP
V!!i!,$Pm!!-!"R!mid!j3!!f3QF[,!!86VVM'"eIrq-[,!L+6VSI@JJV!!3!$@F
!!F)JE!!J)#X!#*!!U!!%28$rp#"X!#!)+!#3!`e@`!*!!!(3E!!b28$rpM!Zrr5
`E[rfCK`JE!!J##J!N!-09X!#3!!"-Llrp**!28(rmQ!'2@lrp2rb-#lrp,"ZrrC
[1L"X!#""k!!-!P$rq`"3!!4#TdKk"-C1ZKlJ6VV9e#"X!#!KD`!)!!3[,[rH6VS
(ILm'6VV[5'!!"*4+E[rdE`!")JaX!I3!-Qd),b`)SNkk(U!JE!!J##J!"3!0Cc!
JE!!J-#J!%T!!E[rd)'`!)$&!!")JE!!J5QJ!%Qi5)'`!)%*S!")JE!!J!QMrh`!
--#lrp,"ZrrCQGN*R,b`!&%kkiJ)GArrM!QcIr`LQ3Q`!1%*X!$)JE!!N3K!JE!!
J##J!N!-0CdBJE!!J!QMrrJ!--#`!"PP!C`T93'F198"R%Q"N1A`!"`!'B&`jI!!
*!!CJ9#mX#*C1ZKhi5'lrhNkkpb`["NkklRCJ!!2#B$J`,!!bN!"Zrr)j3!!b-#l
rmNM!d+`!*#m!,b`!*$mX!$*1Z[PS-#`!-NM!d+`!*#e!rqBJE[rQ3K!jD`!1!#`
JE!!J-#lrp%M!d+J!"#"X!#!K3!!%-#lrlNM!d+X!"#"X!#!LE!!J-LN!$NM"dUJ
!#,+!E$`JE[r#-#J!+&*!)'lr`M&!!#J`,[rZ5-!b,[rZ5-(5U`!%)'`!)#*X!#!
d+3!15-,8U!!)NS+3!)%p32rZ)'`!)#!S!!L3!+X!"$e!rr"`!*!!E[r`1!!`,[r
Zd%463$S!$%8)!'d%1M`(r`JV!*!$$@F33H`)TJ*3[rm!8%!!188!2VTX!$aX(#"
Zrm)`+!!L8N!JE[r#-8!!)Lm'6VVYAQ!!!USpE[r`rqT+E[rUE!4#E[rU-#lrlP0
!28$r`$iZrqTJ-%M()!I3V[ri,8$rjM!X!$T530"%d%G)`)(m#!")3%T!)'lrjK)
35)%CJ3"!8NGT"VjZrm"[bNTX#%"Q6$!X!$a53,"%E3Bj43!mB$Jj4!K)188)D(!
!FJ!#33!"0!!#3J!(jNK$l!L)3I%!!($qj6MP+F%3Ja!jI!!"#%"#E!K#3Q`)4'!
!!G)-E2rr#%4H`'mU-L`)410"3H`)D$3`%!"53VK#9m(!!@F5-#`)410!3H`)D$'
&!!"J!!'H2@`)3[qq3NGJ!!#`-!IQ3%(X#)J2-!!!C`4J!!#D)!IM3%(X#'Jb-!!
!8N'i3@m%B!!!K#!(id!L"9*"3H`)5,*`!!"X!Q"Z)!IM3%(X#'LkF!!!E!`J"q0
!3H`)D$S`!!!J"q0!3H`)5,K`!!"[$#!(id""l!K)1$!!!#!(FJ%#33!"0!!#3J!
(jNK$l!L)3I%!!($qj6MP+F%3Ja!`,!K!8d!j3!K!5Q`)3'B)1Acrr`K#B!a54fN
)[Qlr[Qm!rd`-E2rr#%4H`'mF-L`)4$3"jN*"l!L)!c!J!&E"`!&R"MPmrrm)4%T
%EJJj43!mB!!!UM!X#%*53$e!rqS-EJ!2rqT["Mem!!rrkMeZrqVr[%*(B(i`"qC
!3H`)L!m`!!"Q!Q"U)!Gb!!*"!!%d!!*#!!IQ5%2X#)K"m3!!F2lP118T`4#$%#!
(id""l!K)-B3!!#!(id""l!KS-B8!!$!X#%"53$P!#%!`,!K#8N#`4fB%18F)3Vj
X#%C["$P(#%B-E2rr#%4Q"$P(#%4J$&*(D3LqE[qmE`$rI%TX!$aF`!aXrrm!2PI
"J!&R'%(X#+B#82lr!&!"!#"Zrm)[+!!16VVD(#m'6VVUXNcI'2"1AL"Ih[`!#Nl
3,94$8&p53eBk)(9ZFf9ZG#"NBA4K)'&MDfj[GfaPC'GPC#!SFQ9cCA4dD@jR+3e
QEh*PD@GZ)(*PFf9d6PErmNMR"aK1ZYUX)'hd"#"3*QJ!!L!,Ca!)+`!!#+CR"'!
'B!)Q8f$X)!YQ"NkkfSCJ!Q!#B03SEI3%5QX!2&c!$'[rr`!q9m'!!@F!!MT#Cdk
kkLS3(fF'6VVCJQ$`!Q[qr`LQ3UHTG5`I2@X!22rb3NGJ1$!V!$T530"(5-#"r!J
!5%"+3")c!%!#33$r2`%[+`L16VSC3N+RUA@mRf`)UE4#TkPe,"p54fN'[QlrmQr
#2LX)3Q"L-!IQ3%(V#)J2-!!!Ca+qD`K#CJS`+`K#8d!h3!K#B%!J"q0!-LX!2&*
"3HX)5$3`!!#835!(id""k`K)-B)!!#!(id!b+`!m8N&"k`KS0$!!!*4")!IM3%(
V#'JaJJ!!8dG+4fbD##X!"JLQC`i`+`!qN!"V!$a63$G!!$i`+`!md'X!1P*!5-#
"r!J!5%"+3$G!!$S)+`!'#+CRD!aVrrm!2QCJ-#X!"PG!C`T63'FN9d"R+'!X0h`
!"3!')'X!)%(S!!`#82rq!&!!!6Gm!!J!"Q!10h`!"J!'B!BhI!!*!!BJD`!J)#J
!#&+!)'X!)#&!!!J`,!!D8N!j3!!D,`Y1ZJ$D)'X!)$!V!$a)`0#S!!K5J#"V!#!
K3!!)-#X!20"X!"T53$P!!"SJD`!J-#X!2&*!-LJ!$T*!)'X!)$&"!!iJD`!J-#J
!$V"V!""G`'dB,`"#Cdkkk'`5(b!I#J%!!B!"!N!!!@F',`Y1ZJ"Z0hcrr`!m)'X
!)$!S!!k`D`!3E!SJD`!J-@X!%J!13QF[+`!B6VVE2"SI3UHTG5!V!#k3!*mh3!!
d5QX!0'mD-#X!0%M!,`""qJCL,`J[#bmV!"K1ZYTNB!B[#dkk!""J!2f#60mBi%j
H,Tp1G8j@!!!JEJ!)%A`!!3LR)'hd"#mS!!T1ZYFF6PiZRdje6PErpNMR"aK1ZYI
f)'hd"#"3,@J!![rf5UlrpQFB)'lrpK!S#+GR"'!-B!JJE[rf,9$rpQ$L5UlrpQB
'6VVA`Q!#B!*JaLKZrrBQEI3%)'lrpN)S#+G+E!!'Cl!),!!&#+CR*N*R,b`!&%k
kfQSF(c!X!$C)`#m!3IS&YLm),blrpLmX!"41ZYQN5Q`!1'F!!+J`+`!J8N!h3!!
J$'`!$!!iE94#CcmX!!Br2!!$6VVY!K!ICai[,!LD6VS@D#mX#*C1ZKCJ5'lrpNk
klj4J!2mmB$j#TdKk!HC1ZKC36VV6#LmX#*C1ZKBm5'lrpNkklh"J!2mBB"T#Ccm
X!!Br2!!$6VVXVK!IC`J[,!LH6VS@&%*R,b`!&%kkfE`F(c!X!$C)`#m!3IS&##m
),blrpLmX!"41ZYMf3QFr,!!'2c`!!dkkl'i3(fF%3NGJ"$iX!$)JE!!JF"4)`)(
m!!4"k!!-!N!!$qP)!K$r$i%3)!I3I!!8)'`!+$&!!!SJE!!J,`K#CbmX!#K`$%M
!JI`!!Mm!6VS9RM!I)&ma3!!3)'`!)#m)3UF[#%*R,b`!)#!(d(`!&&*!5-#"r!!
#2`"1ZK9b-"mJAdM!,`#S@L!I)&ma3!!33QF[+`!',b`!(#!(d(`!&$m!,b`!#%k
kjSik(dT&EL*#TdKk!+C1ZK8`6VV-*#mX#*C1ZK8F5'lrpNkklP"J!2hi3UHTG5!
IFKl3J5P!!#i`+`!88N!h3!!8)'`!)%M()!I3U!!%0d!!'$GV!"S!(#"X!#!)+!!
#!!eR)%+R5(S!3Nkk&041Z["-,b`)PNkk&-")E[rf6VVYp'!B$'`!#3!'CK![,!L
@6VS8TNKZrrC1ZZhDB!$pJNcI'1"1ALkI6R8(B@*[FR4PC"p848a1493k)%4PFh4
TEQ&dD@pZ)(9ZFQ9KBfKKBQaP!e4$8%j@rrT)j`!B+'i!#JJX!!F)TQC#$'`"p!!
bE3SGI!!"!!jJ1'!f-#`!-NM!d+`!*#C!&Ui!#6!X!$*53$P!!$)`,!!b5-$3V!!
N*N"#%d)Z!!jJ#'!'(A`!!3!160mB!%jH)&pF6dl36PErr%MR!"JSEJ!+$'`"p!!
bE3JGI!!"!!jJ8!JX!!F)TQBU-#`!-NM!d+`!*#C!&Ui!#6!X!$*53$P!!$)`,!!
b5-$3V!!N*N"#%f!)(A`!!3!1B"C"l!LQ!P$Ir`"3)!![$%kkr&"#,J!160mB!%j
H)&pF6dl36PB!!#m-+'i!##"X!#!aE!!b!")JE!!J3HJ!$!*3rpm!8!!J,`a1Z[`
@+&p1ALkI6R919J!!51F$##KZ!!`q,J!+2#i!#!a(!!&G`!a(%!"H`B!"$%B!!9h
"J!%-4K!!AX'!!@F11A`$k!!51A`"p!!3B!Jj4`!518B!%%cI%-"1AL"I8%p1d%j
@rrT)j`%B*Qi!,$iZ!#K#TdkkkBiSAb!-CL"#TdKk!3K1ZK,H3UG)HJ$b6VS5e%k
kc`a#VJ!`B!!!e#PZ!#!)LLPZ!"`)MLPZ!"J)NLPZ!"3)PLPZ!"!)QLPZ!!`)RLP
Z!!J)SLP6!!JjEJ!U!!ij4`!-5NGQ(N+RUA8J(cP!!!`-E!2S!!aX$$!X!!c3I!2
S18!!$#m-2bi!*MmZ!#41Z[m)1A`!!3!')'`!+#m)3UF[%dkki[`J(b"I))!JE!!
S)9-!"#"X!#!`Kb"X!#!aEJ!U!!)JE!!J-@`!%J!1-#`!0NM!,`""qJ%F,`J[$#m
X!"41ZY8-3H`)TJ*3hrm!8#!!,`a1Z[UZ,8`!-%cI')"1AL"Ih[`!+%l3#Q0[EQj
PBh4TEfi!!e4$8%j@rr`[$%+R6VVSE#KI)!aQ(N+R5(S!L%kk%Ea#TdKk!(*1ZK'
b6VV0kN+Z!#TJ9LPZ!#!)LLPZ!"`)MLPZ!"J)NLPZ!"3)PLPZ!"!)QLPZ!!`)RLP
Z!!J)SN*X!!ijEJ!S!!`[$$mZ!#Br,J!N6VVq$L"X!#!aE!!5!!ijI!!+!!BY6!!
U+&p1AL"Ih[`!)Nl3#Q0[EQjPBh4TEfi!!e4$8%j@!!![,J!)6VVjhL"Z!!JJD!!
J3HJ!$!*3rrF!8!!)6PiZRdje6PB!!#mZ!!K1Z[Qi6PiZRdje6PB!!#"Z!!J`+!!
i8N!JEJ!)-8!!1#mZ!!K1Z[Q86PiZRdje6PB!!%(Yr(BV52aS)'hmD$#m!!%JEIa
S3LJ!!L"Yr'K#+!!$)'hmD%*S!!3JEIaS3QJ!"L"Yr'K#D!!))'hmD%*S!!SJEIa
S3LJ!%L"Yr'JaI!!"!!iJEIaS3QJ!%#"Yr'K#U!!8)'hmD%(S!"K$qJ!D)0NJf5"
Yr'JaI!!"!!`YEIaS!!K1ANje"e9ZDfj[Gfj19J!!)'hmD"!S!!2!,Ik3!'F-6Ud
$5L"Yr'K#+!!$3UhmD%jH6R919J!!(A`!!3!)6Pj1G8j@!!"#,Iai6Pj1G8j@rrT
)j`-!2Li!#%UYr'KQ"'!!!2BJEIaS$'J!"!!'9X!JEIaS)'J!&!aS!!-!"PI"`!&
R!!$8)'hmD$!S!!CR"'!!!-BJEIaS%#J!!QF)6VS0''!!!,3JEIaS$'J!!3!1CK3
r"dkY!iS-4`!0CJJr2!!+6Ud$LJa(!!eQ"(i+B$B-4`!+CKC#Cb"Yr'J[+!!82c`
!$8kkq`3F(f!D$%F!rfB83QFJEIaS,bJ!&$mm!2p1Z[VS("mJEIaS$'J!!3!-CKT
#Cb"Yr'J[+!!82`G1Z[Xb%"pR"%kk$*!!B#a#Cb"Yr'J[+!!82`G1Z[U`%"pR"Nk
k$(CJ%Ja(!!TQ$#"Yr'J[+!!86VVpbNcI!-"1AL"I9%p1d%j@rqj)j`-!2#i!#%U
Yr'KQ"'!!!p)JEIaS-#J!#QFN"%!!qfF!!9463'F!!KK63'F!!Pj63'F!!ea63'F
!!)CJ!!1N$%B!rfB-)'hmD$&m!2m!#Q"U)'hmD!aS!!%!#&E!$%B!$&E"`!&R9$m
'6Ud$LL"Yr'J3+!!5Cd4#Cb"Yr'J[+!!82`C1Z[Rm(Km-4J!0CK4#Cb"Yr'J[+!!
82c`!#NkkqH)H(b"Yr'J-D!!"!!aQ$#"Yr'J[+!!86VVmrQ!!!b3`"J4!!2&R!!#
B@8"R1&P!Cha93'GZ8d"RDP0!CfC63'GL8d"R"'!!!))JEIaS$'J!!3!)C`Br"Nk
Y!iSJEIaS3QJ!#Q"X3QFJEIaS,bJ!&$mm!2p1Z[PX(Kp#Cb"Yr'J[+!!82c`!mNk
kq9JH(b"Yr'J[+!!86VVk+L"Yr'K#D!!+B#iJEIaS-8B!#Q!N)'hmD%*S!!T)HJ+
@6Ud$BQ!5)'hmD%*S!!TJ##"Yr'K#D!!+B!!#EM!'8d"R"P9!CcjJH#"Yr'J`+!!
3C`T63'FF8d"R)'!H)'hmD!aS!!)!$QF),bhmD%kk!UaJ##mYr'K1ZJ,Z)'hmD%*
S!""JGN*R)'hmD#mS!"3r2!$r6VViY"iI3QFJEIaS,bJ!&$mm!2e1Z[LJ(Kp#Cb"
Yr'J[+!!82`C1Z[Mf(KpJ1N*R)'hmD#mS!"3r2!$r6VViH"iI3QFJEIaS,bJ!&$m
m!2j1Z[KN(Kp#Cb"Yr'J[+!!82`C1Z[Lk(KmJEIaS3QJ!#Q!!!D3-4J!"CMSJEIa
S-#J!%'F+8d"R*&0!CaKJ(L"Yr'J-D!!"!!jR##mYr'K1ZJ')B!J[,IaS6VS"bL"
Yr'K#D!!3)'hmD%*S!!TJ!!&B$%B!!@C+)'hmD"&m!!%!%N*R)'hmD#mS!"3r2!$
r6VVheKiI3QFJEIaS,bJ!&$mm!2Y1Z[I#(Kp#Cb"Yr'J[+!!82c`!!8kkq"BH(f!
!!+`-4J!'CQT1V30U3QFr2!!S5'lrlUP`%"pR'#mZrr![2!!!!3"1ZVlL)"mr!%k
kqpaJf%*R)'hmD#mS!"3r2!$r6VVhC"iI3QFJEIaS,bJ!&$mm!2Y1Z[G3(Kp#Cb"
Yr'J[+!!82c`!"Nkkpk3H(f!k3QFJEIaS,bJ!&$mm!2p1Z[FQ(Kp#Cb"Yr'J[+!!
82c`!r%kkpa)H(d*R)'hmD#mS!"3r"NkkpfJH(b"Yr'K#D!!+B&)-4J!"CN3JEIa
S3LJ!%N*R)'hmD#mS!"3r2!$r6VVfe"iI3QFJEIaS,bJ!&$mm!2a1Z[E!(Kp#Cb"
Yr'J[+!!82c`!!8kkpa3H(b"Yr'K#D!!+60m!`%jH)&p86dl3"#T(35S!6PErrNM
R!3JSEJ!)3QF[,!!82c`!rdkkpRBH(d*R,b`!&$mm!2j1Z[CQ(Kp#CbmX!"3r2!!
"6VVf[KiI1A`!!3!160m3J%jH,Tp1G8j@!!!JEJ!)-A`!!3!16PiZRdje6PErrNM
R!3JSEJ!)3QF[,!!82c`!rdkkpKBH(d*R,b`!&$mm!2e1Z[B'(Kp#CbmX!"3r2!!
"6VVfAKiI1A`!!J!160m3J%jH,Tp1G8j@!!!JEJ!)-A`!!J!16PiZRdje6PErr#m
-5(S!(NkY!eT"lIaf+%J[$%kkriJjI!!#!"!SAdjH6R8%6h"PEJ"19J!!5(S!#Nk
Y!eT1ANje"%p`C@i!6PB!!%Kk!!j1V30D6VVjSNjH6R8'3fa[Ff9N!%j@!!"#TdK
k!!j1ZJR16VV'L%jH6R8'9%9-6N98!%j@!!!r2!!Z6Ud$LNjH6R919[rN,bi!$NK
Ym"j1ZYiU$'i!#`!)CJSYI!0RCA6rk'!),A`$F(9drqK)EI!H5'lrk#mZ!!T)HJ"
#UBY#Tcmm!!Y#Th$r,`#TI#eIrq4#TdKZrr#TN5mZrq5TJ`aZ!!(rm'B)2A`!!3!
5B!4#EJ!56PiJAplm!!T1d!!!6PB!!%(Ym"j$qJ"SF!NJf90!E[S-EJ!"!!KQ'NK
k!%K)EI!H%#h`(J*!!2p53$m!6VUl0Q!B5(S!*NKYm"i3,I!H!N!!re*!2`"1ZVX
F5'h`(NkY!eT1AL"I9%p1d!GQB@PXC@3Z#R0eBf0PC@4PC#i!)P4&6%j&9#"84P4
3)&0PFRCPFMSJ4QPXC5"dFQ&ZFfCPFL!!6PEqrM!Z!!a63'X!!KB-3!!&EJ!#$Z0
)-$X!"Nll!*!$$J#1!33"+J&8!I4+VIaSCRC#CdKZr[j1ZXeF%"m+!!!"C`4J!!(
F6Ud$JN+R5'lqrMmm!"G1ZJG`5TpQ&N+R5(S"b%kk#$4"l[lq,`K1ZX5HB$33,Ik
3!'FZ%#hmF3S!!!&R&N(krQJ[#%(krYi[#%kY!d)EI!!"r(&1V305)'hmD"&m!!%
!!f!!!AJJEIaS)'J!&!aS!!-!"QBJ)'hmD#mS!"41ZZ'#)'hmD$#m!!)JEIaS-A`
!!3!%B%)JEIaS)'J!&!aS!!S!"PI!)'hmD#"S!"3-D!!"!!CA`B!")'hmD#"S!"3
-D!!#!!CA`B!"C``JEIaS,bJ!&%kki5aJ!!%#)'hmD!aS!!%!$'B-)'hmD$&m!!)
!$'!+)'hmD$&m!!%!$'!!!0`JEIaS%#J!!fF16Ud$5L"Yr'K#+!!$B!j1V305)'h
mD"&m!!%!!f!!!,*#TdkkpN`VAraS6Ud$JL"Yr'J[#%+R2c`!&cmm!qJr2!(d3rV
mq#m*3rVi$Lm*3rVff#m*3rVmr#m*3rVp%Lm*3rVp+Lm*3rVfcLm*6VVe"#!I)&m
K3!!8)'hmD%)S!")JEIaS-A`!!3!1%#hqN!"R,K!Yr(%+!!!"CaC"q[d!,`K"q[e
f,`K1V30#'h`!!Iaa6Ud$8L"Yr'J4I!!"!!0J%%UYr'KR"%kkpMSEI!!"rT&1AL"
IA%p1d!C848a1493!6PB!!#m-+'i!#%UYr'KR!!$U,``r2!!"U6SJEIaS)'J!&!a
S!!-!"PI!)'hmD#"S!"3-D!!+!!CA`B!")'hmD#"S!"3-D!!"!!CA`B!")'hmD#"
S!"3-D!!#!!CA`B!"C`S[$$mm!!+T1@!),``r2!!#U6S[$$mm!!8JEIaS)'J!&!a
S!!S!"PI!4!!I!+P&,``r2!!&U6SJEIaS)'J!&!aS!!-!"QC),``r2!!$)'hmD!a
S!!%!$&I!4!!I!+P&,``r2!!%)'hmD"mS!!1T45m-2c`!!kNj%#hqN!"R##m-2c`
!"+Nj,``r2!!'U6TJ@#m-2c`!"UNjB%i[$$mm!!0#CkP&,``r2!!%3QHT45m-2c`
!!DNj,``r2!!#U6S[$$mm!!1T1Lm-2c`!"+Nk,``r2!!&3QHT45m-2c`!"DNj,``
r2!!'U6NSAdjH,Tp1G8j@rr`["b"Yr'JJD!!8$'J!!`!'C`4J!!%S-#i!$&0!CaT
63'G-8d"R!!##8d"R!!#Q8d"R!!$qB!!""L"Yr'J-D!!#!!jQ&#mYr'K1Z[R5)'h
mD$&m!!%!%'!5,bhmD%kkqKiJEIaS-A`!!J!3B!!!d%*R)'hmD#mS!"3r2!$r6VV
`,"!IC`C1ZJ(bB"T#Cb"Yr'J[+!!82c`!pNkkm(J3(fF%6VS"eQ!!!*C#Cb"Yr'J
[+!!82c`!rdkklr)H(d*R)'hmD#mS!"3r2!$e6VV`4KiIB'T#Cb"Yr'J[+!!82c`
!rdkklmJH(d*R)'hmD#mS!"3r2!$d6VV[Y"iI3QFJEIaS,bJ!&$mm!2p1ZZqJ(Kp
#Cb"Yr'J[+!!82c`!mNkkli`H(b"Yr'J[+!!86VV`AQ!-)'hmD#mS!"41Z[+Q,Kp
1AL"IA%p1d%j@!!![$#KZ!!K+VIaSCK)[$%*RU6S[$$mm!!&#CkP&B(![$$mm!!%
JEIaS$'J!!3!19m"%!"m!U88JEIaS5QJ!%'B+,``r2!!"U6PJ##m-2c`!!DNk)'h
mD#"S!"3-D!!$!!CQ##m-3QHT1@!',`a#CkNk)'hmD!aS!!%!$'B+,``r2!!&U6T
J##m-2c`!"DNj+&p1ALkI6R919J!!5UhmD&I!)'hmD%US!"4A`B!"Ca"#TdKk!'"
1ZJ-86VUk#'"!5(S!5%kY!f)JEIaS5'J!'%kY!f*)HJ!`6Ud$BL"Yr'JJD!!8,bJ
!#%KYm"j1ZYGU5'h`(NkY!f*)HJ!+6Ud$@NjH6R8"+3)J+!!)9'mJD'pcG#!!'e4
&6%j&9$SJ6QmJEh"PEL"MEfjZC@0dD@pZFdj@!!!JEIaS%A`!!3!#3UG)HJ!X6VS
#KN+R5(S!$Nkk!Ra1ZVNJ6Pj1G4*2GA4`GA3JBR9QCQ9b)'CeE'`!"P4&6%j&9!"
19[rm,`G#TkPe,Kp#Tbm(,c`!!2rrU&JZ(`b(!!!$k'`'hV`!!!2S28F!##iI6Pj
1G8j@r[a#TdKYrC!!6VVAE#eIrra+V[rmCLT#TdKk!5K1ZJ)%3HhpN!![#%+R5(S
"$%kk!I41ZVRD6VVaTN+Yr'a1ZX-b$+i!N!-"rraQ'N+R5(S!bNkk!G"1ZVM%6VV
aJN+Yr'a1ZX-1)'hmD#m)5'hpN!!r2!!"%#hpN!!#3!$r2`")E[lm6VUcG#"I3HJ
!'%2Zr[a`3#$C8d"ZqL"Yr'J[#%+R3qlrr#m*2bhpMLm)3QG1Z[mH-"mJAcm!2c`
$k$mm!I4$q[FU,`P$q[*X,`P$q[%f,`P$q[GD,`P$q[G`,`P$q[H),`P$q[%X,`P
1ZZif)"mJAb&!!"41Z[hi5(S!&%kY!f*#VIaX6VV#DNjH,Tp1G3P8FRPTEQFZN!-
F6Q&YC5"cCA*fCA*c)'j[G#"bCA0`EfjND@jR,J!,)'j[G#"VEQphELi04QpbC@P
REL"SEh0d)%j@!!"+VIaXCa4#TdKk!+"1ZJ$!6VUhY%+Z!!jJBL"Z!!T$lIf3!("
!)YK63'lk1fi!#2f13UF[,I2m3IVqFLm)2c`-!%Kk!'"#Tdkk`*BVAraX5UhmE'B
H3UG)HJ"#6VS!FN+R5(S!)Nkk!'K1ZVbJ3Ui!$Q!+3UG1ZZq!,9m!$NjH)&pF6dl
3&'jKE@8JFQ9cEfaeG'P[EL"dBA0V!!C848a1493!"P*PFdjKE3!D3@abC@&NH5"
dFRPTEQFJG'mJBfpZEQ9MG#i!)'m!"#kI6Y!JAbkI6Y!LAd+"-KmJAe1"3S$3@'3
#8S"4bIri2S"1d5"i!Ul3r!!+6Y!!!!PD!UJ!!8j@rr`["kN`2A`")2rmF!%G[!!
8!2a#Tcmm!!&)E[rmU6%VArr`,bhrm#mk!'5T68+R2c`"!DQr+errp%+R2c`"!+Q
r+errq%+R2c`"!UQr+errr(i"B!iJ"q9!,c8!l%*RU6954`a(!!4[l+Nh%#hqN!!
+!!!"C`J[,Ir`3QHT1LmYrr3r2!!&U6NZ(djH6R9%8PC56PB!!#"m!!!*##e3!!K
1ANje6PB!!$Ym%!$r`MYm%!$aaN+R6VVrfM!YmFE3EIr#d(`)!0"m#!$3I"J!d(`
-!0"m#!")`#)INS![!8kY!HT1V3&L6Ud"DNkY!@T1V3&U6Ud"DNkY!@T#Tbki!US
JAbm)3UFZZ!+U)"mJAb)3NS!-J3!"N!!!E!C#,Ik3!'!''h`!!Ik3!%(YlVS[#+K
ZU2ir22rr3QFJ(k!bU4*1Z[l!3UHTHkK3UFa"lIr-3qhZ4L$C)0P)EIr%2c`!"$m
m!"J`,Ir5@8!r!$!Yrp"C3$m!U+G1ZJ"m3QG1V3&b5PpR%%+R5(S!6%kY!K*#Cdk
Y!+T#TcmYrm*1ZJ"m+erd!%kk!141ZJ1%6VS#X%kk!c41ZJ@k%#hqN!"R"%kk"J!
r2!)!6VS'%%kk"hC#,Ik46Pj1G4p$B@iRG#"[F'9Z)%eKBb"3FQpdEf0[E#"3B@0
VB@GP6PB!!%+R2c`!-8+R3UHTI#YIm4j#EI!83Uh`&N+Ym"T1ANje6PErqNMR!3K
#EI2k3Lhbq8+YmZ``,J!)d(`!%$i!)!F#3!!"C`*54cm(6Ud##N(YmY`S5#Y-mra
#,!!16Ud#`Lm-5(S!*NkY!U)j4`!)3Hhbh#P)!!ST6!!%,8`!#NcI%)"1AL"I9%p
1d!40B@PZ!%j@!!"#,Il"3QhqYN+YrVK#VIkm3UhqXNjH6R919[rd51F"##KZ!!K
#CdKZrr4)E[rf6Ud!mNTIC`4#E[rd-#lrp%M!d,b!!J!!+)!TI)!#!%!!"#PmJ!)
!3!!)+Ab!!J"!!!a"l!!33rS!X#$C)0N`N8(X!4*$qJ#D)0NJf4Pm!!%"%%*R5(S
!G%*R5'lrr%kY!-Sq(dT(CPJYI!!!!K,rq%*R2blrr$mm!!&#TdkY!ISq(dT(CK*
#CcmZrra)E[ri,`a1V3$52Kp#CcmZrra1V3$#2Km3,!%3CaC#Tbm8,ccrN!-!U&J
`,[rd5-$3RbL!60m3J%jH,Tp1G44$GA0dEfeTHQ&dD@pZ)&CKE(9PF`!(9@jVEQp
hEJK"F("XC8e"3`"19[rm3QhZmN*Yl["#EHlZ3QhZl%*YlZT#EHlS3QhZjN*R5'l
rr%KZrrj1V3$b5PpR!Q"Z'flrrHpZ1flrrZpXF%JE31p[3Qh[D$!m!2mE31pUF%J
E31pV'hcrrqle3LhZp%+Ylh"#Th!B,`"1V3'#+er[C%+R6Ud!iLYIlh4#TbmYmra
"l3!U,`Jr2!J!5(S!&N+R6Ud#+LYIlhJYEHpi!!K1ANje#%&58#"5C@0f!%j@rri
["d*(B"SJ"d(Ylj6R3%+`!!!J"d(Ylj6R3%+`!!454`a(!!p[i%*Ylj)lI!!$lia
#VHq)1h`!!Hq!3Uh[K%*Yli*#Tcmm!!&"l3%#,`K1V3&#+er[MNUYlijQ'%+R5(S
!(NkY!K*#TdKk!""1V3)56Ud!@LiI6Pj1G3**8!!%58008!"19J!!3UFr2!!$3Hd
!qLm)6Ud"3LYIlha+VHpmCKK#TdKk!#*1V3)53UG)HJ!16Ud#%NkY!&T1ANje#%P
ZG'9b6Q9d!!0(4e"19[hB51F2#%(ZrHJ[#%kkr@JVE[hSmY3VE[hXmYJVE[h`mF)
VE[hdmEj"lI'd3qlpq#$C)0N`N4YZr[Madd(YmG4$l[lkF%!Jf90!E[T#EI&H3Qh
aLMYm!!(aL%*YmD"#EI'H3QhaR%*YmCT#EI'B3QhaPN*YmC4#EI'53QhaN!"#EI'
13QhaM#Jm!!#!!%+R,VJ#UL"I,`K#Tbki!USJ(b"I)K#5J#m",`41V3*U+KpFK6`
m!PK#EI()3UhabN+YmFj#EI'U3UhaV%+YmE!J"91!28$pf%*(B'"#Th!3,`"1V3'
#+&mJ$'B33UG)HJ%d6Ud#%N*R6Ud!UN)X!!4#V!!+3Q`!$N+R5-B["NkY!B)TA`!
'5U`!"QB33UG)HJ$D6Ud#%N*R6Ud!ULm-3HhaULm)6Ud!LP*(D3DqE[hBEjT#Th!
D,`"1V3&k+eraTL"YmDBJ8%*S!!*#TbmYmra"l3%5,`Jr,I('5(S!KN+R6Ud#+LY
ImD)[,I'L3UG1Z[d#3Hhab#m)3HhaULm)6Ud"@Mem!%MpjN*R5'lpjN(Y!9)[#%k
Y!(*+AfFB3UG)HJ!d6Ud#%N+R5(S!%NkY!K*1V3"D60m3m%jH6R8@FfpMDf9d)'C
[FL""F("XC@*eFb"*8!!04%436h"PEP0[BfYPG!G*8%4PEA9i+&*KEL"[GA3JEfB
JF'&MDf9d)(0dEh*KCf8JC(9bD@jR)'PZAfPZDA3!(QPZAfPZDA3k)'0KELGd)'e
KDf8JCR*PC5"aG@9eC3"19J!!3UhZ`N*YlXC#Tcmm!"&"l3-D,`K1V3&#+erZ[NU
YlVjQ'%+R5(S!)NkY!K*#TdKk!!j1V3)56Ud!@NjH6R8)5@jdCA*1CA3!!e9%8%j
@!!"#EHlN3QhZb%+YlXT#VHl16Pj1G8j@!!![$%+RF#S[!%kY!B)VAr3%5Uhd"'B
33UG)HJ%`6Ud#%N*R6Ud!ULKYp!4#Tcmm!!C"l3+U,`K1V3&#+9m!"NUX!!CQ%%+
R5(S!j%kY!K*#CdkY!+T#Th!+,`"1V3'#+*p+P'B33UG)HJ#D6Ud#%N*R6Ud!UL"
83P!J9%+S!!)J9%+S!!C#E!!83Q`!&N*X!"K#E!!D3Q`!(%*X!#"#E!!L3Q`!+%)
X!!4#TbmYmra"l3+b,`Jr2!`!5(S!3%+R6Ud#+LPI!!T#TbmX!!T"l3+k,`Jr2!`
!5(S!'N+R6Ud#+LPI!!j1V3)k+&p1AL"I9%p1d!G83e"`FQpM"e4$8(0PEQ3T9%0
3AdP1593k)'0KELGd)'&XE'pMBA4P)'0[EQjPBh4TEfiJFA9PG@8G9%03AdP1593
k)'0KELGd)'p`C@iJ5@jdCA*ZCA3E9%03AdP1593k)'0KELGd)'GPG#"RE'pLB@a
c6PB!!%+Yr'K#,Iaa3UhmE%kY!h*1ANje!!!%$!+`!!T19J!!3UF[,J!)6Ud")Le
I!!a1ALkI6R919J!!)#i!#&#!,8!!$%jH,Tp1G8j@rri["c!Z!!T33&*!2J!J"`*
!!!&R!P0(3UFr"cmZ!!K1V3%D,9m!$#iI6PiZRdje6PB!!#mZ!!K1V3%U6PiZRdj
e6PErpNMR!"JSEHl#)!aR-$!X!!5`EJ!39m!b,!!'XQi!%PI"`!%L,!!)XUi!&&I
"`!&R"N+Z!"KJCLC-+&4Jc%+RF"3[!%kY!B)SAb!-CKj#TdKk!'C1V3)53UG)HJ"
36Ud#%NkY!&*#VJ!BB$*+VHl#C`3QM'!%+dcZ`N+81@i!%!!%1@i!%J!'+@i!&!!
)+@i!$!!-+@i!#!!3,8`!'%cI'!"1AL"Ih[`!%%l3#Q0[EQjPBh4TEfi!!e9%8%j
@rr`[$#KYlX)J$'FJ-#`!"V"Z!!KA`#)X!!LbVJ!+9m(!!@F%B!CJ!LK8B0`Y6!!
1+&p1AL"IA%p1d%j@rra)j`!B*Qi!##!,CJ*J-,IYlX*Q#L"YlX)V81l#B"SSEHl
#)!aR#VI8CJ*J"#K8B2)J$'B#B!JSNbm,6Ud!JNcI'!"1ALkI6R919J!!5QhZaQF
-2@hZaJ!)8QhZaQ!N3QG#TkPeU'XlAql'$'d$k1l'E!B'E32SlXBpEHl'!!K5EHl
'6Pj1G8j@rpK)j`mB,#i!$NkY!'*#Tbm'6Ud"-LSI3UF["8kkrH`SAcJX!!5iEJ!
-E`S["NkY!5TJ!!$m2L`!"NT(C`!!N!!3,J!0!N!!!@F5-#i!$%M!d)`Y32rD)'l
rfN)3,@i!#2rX)%8YD!!3rr"`!"e!rr4`%4e!rr8pE!!%rrC#Cd(Zrq`[#(!-5-#
"r!!#2`"1V3"U19m!"N+R3QF[$$!Z!!a53%M!JI`!!Mm!6Ud!DM!I5-![!+KD)"m
p32rQ18F!"VjZrqCR##m'6Ud"+Q"L*QhZ`L!,CcT+D`!%9X!b+`!%XQ`!!PE"`!&
R"#C6B#"+U`!-CaJ["L!%88!r!#mZ!!J[+`!3,bX!$%kY!%TJ)Q$#3QF[,J!)3UF
["NkY!6)r2!!$6Ud"#MeIrpJ["Nkkr6T-haM`6PiJAplm!!T1d%j@rqC)j`FB+Li
!$N+R3UF[,J!+6Ud"-Nkkr,)SAc!Z!!K33$i!)!F#3!!"C`T)ab!-d)FQ3%)6)%8
X+!!)18F!"#"&1+J!"#"&1@J!"J!#3UF["NkY!6SYArr`,8Erp(!!(8$rq(!4(8$
rq6eX!!6rqN*R3Hlrm#m)F!a)`)(m!!)r!%kY!'SjA`!'3UG#Cbm-)!G53%M!JI`
!!Mm!6Ud!DM!I5-![!+KD)"mj3!!'3QF[,Hkq,bi!#Mm(,`C1V3&+29m!%NcI'1"
1AL"Ih[`!#Nl3!!!&f!-!!!419[ri51F$##KZ!!J3&!*!!2pV',"m!!pZ%N(k!5)
b!1C*4%%"-"$r#M`!"'F)3Ui!$'!!!1K#Ka!8!N!!rce!rrKm!Q!!!-S30'!!!N!
!rfXBX(`!2fi53IS!iM)!jNP%33%`%2m+2!!%CKSJ"qQ!%M4J!!*"!2q5I!!`5-(
5J#i"B!!!L"!dB!!#3!$rDaL`I!"[EK*"qJ#F-J$Q584"!6!3r`Sm!!4Q(#!(kB!
50'!!!N%!rj*m!'(5I!!+5-(5J#i"B%B30'!!!N!!rfXBX(`!6fi53IS!@$)!jNP
%33%`%2m+2!!%CK`J"qQ!%M4J!!*"!2q5I!""dR`!#NM"dS!Z!@!')!ITJ#i!8NC
T#,aZrrK[!2mb,8F!$%cI%-"1ALkI6R8!IJ#3$!2r!*!'!ra19[rZ51F2##KZ!!K
`!4fmri!!mA!#(E`!!J$aF!0#0J$aF!4#0J$aIJ&m!@!!!)C#44!8!N!!rl"(A-!
50(!!!N%!rfXBXR`!2fi53IS!`M3"jNT%3J-`)2m+2!!%9m(!!@FN)!AR3")dF!!
#33$rdN#5I!!`1J%-43$rE`C#VJ!-B(T54f#U)!BGK3$l1!B3&!*!!2q`4fdL%$4
`!!*!!2m-3!!XCJ454f!'3Ui!$'"-8NB-4J!%E`$rGK!8!N!!rl"(E3C#VJ!-B$)
pI!!%rrTJ($!ZrrS50N$l!N%!raf"!2&64!a%!!&Y$&0ZrrS-EJ!"rrTXh#eZrr)
!$%cI%2"1ALkI6R8!r`#3"Nj@rqj)j`m)+'i!#(!"(EcrJ!$aF!)G[!!#!2&`!d)
f!2&`"%)f!2&q!A`"B!!!L%*&%"3#3!$rX%GF`")dF!!#33$rDaLbI!!rEK*"qJ$
%0!(Q5N4#!c!Jr`Sm!!4A`F!"CbBJ"F(m!!S50(!!!N%!rp*!NR`!-$S"$%8!rfm
'3Ui!$'"k8NGJU#!'(B8!qcJ'%"3#3!$rX%GY)K!dF!!#3!$r$%!!,QB%8NGJ"N+
Z!!aJ6&*'$%B!"'m!rh33&!*!!2q`4fd'3Ui!$'!b2A`!"2rkB"``,[rk%MC!q`*
"!2mGJ3$a8d3-4!!"E3a6E[rk$'i!!IrkE0`YE[rb!!a-ha$`6PiZRdje!rm!N!C
19J!!,bhYdNkY!M*1ALkI6R919[rX51F2'#"Z!!J[#%Kk!Fj1V3+#%"pR#LeYmY3
!$'!!!C`JEJ!)%"!#3!$r2!"#Tb!'9N!r!%*R6Ud#dLCI)!YQ)%+R5(S"KNkY!K*
#TdKk!B41V3)56Ud!8N+Z!!aJ!!&F3UG#Td+R,`Y1V3%b6Ud#qNkY!ZSSAaLm!!&
5M"L'8S`p4[rXIJ&J&#"Z!!J3-(!!!N!!raL!8Sa54fN'[Qlrl'rQ3K3VEI2mlG*
#VHh@3QhYlN*(B'`-4`!"CJ*JDL!(3HhYfZ9!51H!J%+R)JG$lI(#j8%[-4!!2c`
!+NMRJ)"#CdkY!`Sb(dcI!3%r!82k!1i[#8+R6Ud$!L)I60m"!5'"!!"#Cb!(3Hh
YfZ9!,c!!!#m,)!C@3$m!6Ud$%MJI8NF-4`!"Eii[#dkY![*#TdkY!N)U(dU&CKj
#TdKk!)a1V3)53UG)HJ"m6Ud#%NkY!&*#VJ!-B'*`#5m!3IVqM#m)3UF["8kY!PT
1V3)D3QF["8kY!NS3(dL!1!"#Cbm&6Ud#8K!I5)!i!%*(B")J"d(YlGVP3#m`!!"
1V3,L8NF-4`!"EqK+EHhZCJK`!5e!!!aJ"LeYlGB!$%cI'2"1ALkI6R8&G'PYCA)
(98436N&043C`B@0VCA3!!QeP!%j@rrK)j`!B8QhYlN+R3UG#TbmZ!"*1V3%b6Ud
#qNkY!ZSSAa!X!!%#3!$r5-$3M&5!+%!3&!*!!2m-3!!#9m"+VHh@9m(!!@F53UG
1Z[fq)!a8J#C!+e2YeQ!1$'d!!HhZCJC#TdkkrD3[,J!56Ud#mNcI'!"1AL"Ih[`
!$Nl3!!!IL!-J!!019J!!3UG#Td+R,bi!#%kY!6*1V3,k6Ud#kLeI!!a1ALkI6R9
19[rq3QFJEJ!),bJ!5N*R6Ud"XMeIrrj+E[rqC`JGI!!"!!aJ&#"Z!!JJD!"+5UJ
!(&I!4!!G3!!-6PiZRdje6PErqNMR!4JQEJ!)+'i!$%*(B#*+&'BD,bi!$#!,8S!
[!%M(,`G1V3!b+%XBKf!1B!*5M&*($%F!rfrB3K0-haL!6PiJAe"26Y"19[rm51F
"'#CZ!!`Z,J!))!Y5J#K!,``["a!6!N!!rdM!,`"1V3!b%"-#3!$r5-$HJ#"(3K"
-haL!6PiJAe"26Y"19J!!51F!'#KZ!!`QEJ!)5P0Q6N*R,b`!5N*R6Ud"NMDI5P0
R"'!!!Aj"l!"5)Q`!5L0)!")JE!"+-@`!8!!@)'`!5N)S!"T#CbmX!%T#CdkY!D)
fRdT6C`4J!!&+0Vcre3a6rp9Q!!%D3H`!8L*X!%SM5!!5)'`!5M&X!&!!&L"X!%T
#+!!D3QF[,!"+3QG1V3'D0Tp+8fB!!*!!3H`!8L*X!%SM5!!5)'`!5M&X!&!!&L"
X!%T#+!!D)'`!5Lm)3UHTG5!I)&mK3!"))'`!5Lm)3UHTG5!I)&mK3!"-)'`!5L&
m2j!%!#3JE!"+3QJ!+#"X!%T#D!!Z$#`!!3"1CJiJE!"+)Aa"8&"-!#"J$#"X!%S
KI&4&@&3!)%*R,b`!5N*R6Ud"dMDI5P0Q9%(X!&)LE!"+)dJ!%L"X!%SaE!"3!"B
JE!"+3LJ!'L"X!%S4I!!$!"XJE!"+3UJ!(%SX!%jQ%%*R,b`!5N*R6Ud"`MDIB!j
#CbmX!%T#CdkY!ESfRdT6CL!JE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!GS
fRdcI'!"1AL"I8%p1d%j@rra+EJ!-CcC#Tb"Z!!j)D!"56Ud#%MmZ!!a1V3#b3UH
TG5YIlZ"+VJ!)C`J[,J!)6Ud#mP0YlZ4#,J!5B!BGI!!"!"*1AL"Ih[`!#Nl36PE
rr%MR!`JSEJ!+1@i!#!!3$#`!!3"1CJ!!d%*R,b`!5N*R6Ud"NMiI3L`!6N(X!&)
LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'L"X!%T#U!!F$'`!#J!QCKSJE!"+%A`
!!`!E3QF[,!"+3QG1V3(#2KpJ2#"X!%S4I!!"!"Y#CbmX!%T#CdkY!F)q(dT(CL!
JE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!GSq(d*R,``r"d+R6VVqp"`I)!B
+!!!"CaS[,!!',b`!#Mmm!!&#TdKk!3K1V3)56VS"l"e'!!jJ!!$+$#`!!J"1CJ!
!`"Pm!!%!6N(X!&)LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'L"X!%T#U!!F$'`
!#J!QCKSJE!"+%A`!!`!E3QF[,!"+3QG1V3'k2KpJ2#"X!%S4I!!"!"Y#CbmX!%T
#CdkY!ESq(dT(CL!JE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!GSq(d*R,``
r"d+R6VVq+"`I)!B+!!!"CaS[,!!',b`!#Mmm!!&#TdKk!"T1V3)56VS")"e'!!j
-ha$!6PiJAea26Y!K3fpeE'3JEQpd)'0SB@jRC5"dEb"bCA0[GA*MC5"QEh*V(80
[G@aN)'j[G#"MD'&ZCf8JG'mJC'&dB5"QEh*V6PEriNMR$aJi,J!)3QF[1J#kUC`
m(d*R3UFr"%kY!J*+AfF%B!!!Q%*R,bi!#UQA2Km-4rrrCJ4J!!#%3QF[1J#+UC`
k(la&E("#Tbmk!(`r2!!"UCdSAb!-9m"R(Lm!3QF[,J!+2`4)E[r`6Ud!kM)I)"p
+39E"J!&R"Mm(UCTJ2#C8,92rp%+R-#lrq%M!,`![2!!!)!#S@b!I28$rq%*R,bi
!#Mm%5'lrm%kY!I*+AfF'2`HTQQ!%2`HTQNcI'2"1AL"IA%p1d%*14%a19[rd51F
2'$`Z!!`U,J!)IJ4#TbmZ!!j1Z[SS*Pmf[!!&0dB!!NT'CL!S45m&)!YBJ#m!6VV
kZL"&%"!#3!$rd%G53$i!B!!![M!'D`!!P!a!!!GZ!!#-idJ`1`!'6[X!N!-5!#!
!,J!m!%S!@!"Q!(4#TdKk!6K1V3)5+&pJEN+R5(S"'NkY!K)SAf"J3UG)HJ$k6Ud
#%LKIB&*#TdKk!1*1V3)5+&pJ4%+R5(S![%kY!K)SAf!f3UG)HJ#D6Ud#%LKIB#K
#TdKk!(K1V3)5+&pJ'N+R5(S!A%kY!K)SAf!-3UG)HJ"!6Ud#%LKI,``J#eL!,`"
1Z[Ri%"3#3!$rd%G53$i!3QF[,J!5,bi!$Mm(6Ud$%MJI60mBm%jH)&rHr!!16Y!
09@jVEQphEL"PFR*[FJa1Eb"cG@0S)(9cCA)!%dCTE'8JB@abC@&NH5"PH'PcG(-
69@jVEQphEL"dFQ&ZFfCPFL"*4"C*E'aPCf&X)&4'9&!JEh"PFQ&dD@pZ!!P%DA0
V)'CeE'`33@0MCA0c)(CTEfaKG'P[EJ!14QPXC5"ZEh3JCQpeEQ3!'&4SC5"+6N-
J6@9YEh*TB@`J3P9(5%&-9!"19[rq51F"##KZ!!K#CbmX!"K1V3*+(KmjI!!$!"`
[,!!86Ud#-NcI%)"1ALkI6R919[rm3UF[,J!)6VVi2LeIrr`J,[rm@)!Y3!!-6Pi
ZRdje6PB!!$Ym!!(ZdNjH6R919J!!3QhZdNjH6R919J!!3QhZdN+YlZ"#Td+R3QF
r2!"&3IS!6Lm)3UG1V3-#+erZe%UYlY4Q%%+R5(S!)%kY!K*#CdkY!+SVEJ!)lY`
VEJ!-lYK1AL"I8%p1d"9$B@iRG#"[F'9Z)&9%8#"cEf0VCA419[h551F2'#CZ!"*
1V3"L3UG#Tbm,6Ud"-NkY![SYArhH3UF[,J!-)'lphMm36Ud#fNUIC`S[#dkY![*
J!!9J)#hZi,#V!!T[#Lm,6Ud#mQ!!"8`-E3!"lZ4Y,%kY!MS-E3!"lZ4Y)#mYlY3
[#d*R3UG)HJA36Ud#%Nkk&C!!,`Y1V3,bB!!&'&*YlZ4#Tbm,6VVh&LiI)%FL4c+
3!#"($&!!!Qm1,`Y1V3,b8fhZj'!!"1iJ"e5!,`""l[lb,`K1Z[G-)!G8J")Zr[)
#33$r5-(5J&+",`&"l[hb,`K1Z[FZ3JC#CdKZrI*)HJ983QFI2!!"6Ud!NK!IC`C
k!R`"B(a#CdKZrI*)HJ8`3QFI2!!"6Ud!NK!IC`Ck!R`"B&j#CdKZrI*)HJ8)3QF
I2!!"6Ud!NK!IC`4k!@"#3QG)E[hb5(S%iN*R(c`!!8kY!*)3(fF'HJ9m!Q!N,bh
Ze#m,3QG#TdKk",C1V3)56VS8S#m,6Ud#mP0YlZ4J!!3N5QhZdQBN,bhZe#m,3QG
#TdKk"'T1V3)56VS8GLm,6Ud#mP0YlZ4J!!2k)%F-8!!"CJ4i#f!#H!T#CbmZ!!a
"l[lb,`Jr"#mYlYK1V3"#5PpQ,#mYlY3[#d*R3UG)HJ3-6Ud#%Nkk&#T#TkPe+er
Zi#m,6Ud#mP0YlZ4J!!1Q3UFr2!!,2c`!!8kk$Z)SAb!-CLC#TdKk!m*1V3)53UG
)HJ1q6Ud#%NkY!&)[#dkY![*6EHlNB!!$E#PYlY`!(N(X!&*$l[lbF%!Jf90!E[S
C4J"13Q`!8$P&!#4#TbmZ!!`JE[hH2a"#CdkY!`T"qK(Z,`J[$%kY!`)TA`!'5U`
!"QBb3UG)HJ056Ud#%N+R5(S$4%kY!K*1V3"D3UF[$%kk%$SYArh8,`Y1V3,b8fh
Zj'!!![!J4`a3!!&Q!!'#1A`!#`!Q1A`!!3!53H`!8L*X!%SM5!!5)'`!5M&X!&!
!&L"X!%T#+!!D)'`!5K&m!!%!'b"X!%T#U!!F%#`!6NL!C`j63'GB8d"R!!#LB!!
!a%*R,b`!5N*R6Ud"`MeIrG*+E[h5CMC#Cbm-6VVdT"!IC`JpI2rCrG*J)L"X!%S
aI!!"!#`JE!"+3UJ!,N*R,b`!5N*R6Ud"fMeIrG*JG%*R,b`!5N*R6Ud"ZMeIrG*
+E[h5CMC#Cbm-6VVd9K!IC`JpI2rCrG*J5L"X!%SaI!!"!#`JE!"+3UJ!,N*R,b`
!5N*R6Ud"fMeIrG*J*N*R,b`!5N*R6Ud"`MeIrG*+E[h5CK"#CbmX!%T#CdkY!C)
pArh53QF[$$mZrG)[#dkkpR!3(`S!!!&R0#mX!!B[,!!+2c`!!8+R5(S"[NkY!K*
1Z[PU3QF[,!!H6Ud!1N+R,`a1ZJl),9rpe'!!!BK#TbmYmra"qJ2i,`Jr2!J!5(S
"S#m-6Ud#+LPI!"4J!!&J1A`!#J!Q1A`!!3!53H`!8L*X!%SM5!!5)'`!5M&X!&!
!&L"X!%T#+!!D)'`!5K&m!!-!'b"X!%T#U!!F%#`!6NL!C`a63'FN8d"R2'!!!*K
#CbmX!%T#CdkY!F)pArh5,`a)E[h56VVcpQ"k3QF[,!"+3QG1V3'k29rpdLm-5'l
pdNkkmpTJAN*R,b`!5N*R6Ud"`MeIrG*+E[h5CK*#CbmX!%T#CdkY!C)pArh5B$B
-E[r9rG*Q,N(X!&)LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'N*R,b`!5N*R6Ud
"QMeIrG*#Cbm-2blpdLm,6VVe(K!I#J!!!@Fb,b`!"LmX!!Sr2!!#3UG)HJ"X6Ud
#%Nkkq"K#CbmX!"j1V3!k3UF[$%kk$ABYArh8B$C#TbmYmra"qJ$U,`Jr2!J!5(S
!-Lm-6Ud#+LPI!"41V3)k1A`!!3!3,`a#Cdkk#R)[#dkY![*-haM`6PiJAplm!!j
1d!4dCRGb!"G$Eh9XC#"ZEh3JEh"PEL"dD'8JCQPXC34dCR*N!!094&!%9%C88!!
+BfpZEQ9MG'P[EJ!49(*KER0QCA)JFQ9QGA0PC#iK9(*KER0QCA*c)'&bC5"ZEh3
JBQ9TEQFJB@0MCA"dC@3Z#%*KC#"YEf4P!!PYB@0TER4[FfJ)EQ9dBA0MD@N!"@P
YB@GP"@pMG'9d&94[Eb"YB@jj)'0[EQjPBh4TEfjc,Nj@rrK)j`F)+'i!#$Pm!!%
!(!aX!!%!('B'6Ud#'Q$b$'`!"!!FCJ!!LNTX!#*[1%*R,b`!"LmX!!Sr,!!16Ud
$%MSI3QF[,!!B6Ud#5K`I,b`!1N(k"4J[##m-,b`!'%kY!Q*JTQ"+3UG)HJ&-6Ud
#%NkY!BS-E!)!!#aY'#mX!!B[,!!+3QG#TdKk!4"1V3)56VVfH%*R,b`!(NkY!$T
#Tbm-6VS,eLiI8fhZj'!!!,S-E!!)!"aQIJaX!!8!*&I!5L`!6PE"`!&R-MPm!!F
!(%*R,``r2!!"6VVcC"!I#J!!!@F@3QF[,!!H6Ud!1N+R,`a1ZJZ',KpJEQ!k3QF
[,!!B6Ud#5K`I2c`!!5mX!"j1V3!k3UF[$%kk#f!Z(dU(CJj#TdKk!&"1V3)56Ud
!SP0YlZ4J-JaX!!F!('FQ3QF[,!!B6Ud#5K`I3QF[,!!H6Ud!1N+R,`a1ZJXJ,Kp
6EHlNB!4J!2kN6Ud#)NcI%1"1ALkI6R8L9(*TC@3JG'mJG(*KER0QCA)JHQ9bEbe
XC@jRG'JJCQPXC3!E9'p[)'eKERNJFQ9dFQPPFb`JCfPfD@jR)(9`"&4'9&!!6PE
rc%MR$aJSEJ!)3Llrc#im!!!#!%)%3Q`!%#eX!!VrkN+R2c`#!%*R6Ud#dLCI)!Y
Q)%+R5(S$CNkY!K*#TdKk!f*1V3)56Ud!8P0YlZ41V3)L1A`!!J!F%#`!6NL!C`j
63'F+8d"R!!$UB!!"&!aX!!%!*'G%)'`!5Lm)3UF[#dkkpX)J(b"I)8!!)#"X!%S
KI!!!!J!!*#"X!%T#D!!X3QF[,!"+3QG1V3(+2"mJE!"+,LJ!+'!!!-JZ2!!!!J"
#Tbm,6VVfILSI%#lrc'F13Llrc#"&%,`!#P1(8S8JE!"+-A`0J!!X5SG[@L"X!%S
K43!J)'`!5L&(!#4#CbmX!%T#CdkY!FSm(b"X!%UHU!!S5NCR!Q!Z)'`!5L!S!#K
6J0U!)%8-%!!0CKK+KfB)(A`!!Ir-B!a5K5"&%,`!#P1(8S9JSL!m!!!#!*!!Kbi
!B#j+E!!3CL4#Tbm,6VVejLeIrr4#CdKX!&)r,!"3,blrp%kY!1Sm(hi3B!4#Khc
C5NC@`!a'rpP@`F!"Ca4#TdKX!&*1V3)52`C1V3#kI0P#Kb!(d+`!+#P!!#K+E!!
3CJBjI!!'!"`-E!!#!"aQ"NkY!KTJmJaX!!3!('B!!*T+E!!LEd*#CbmX!!B[,[r
U2b`!$NkY!a)pArrN3QF[,!!B6Ud#5KeIrmd[,!!k3IS"VLm),``[,!!B6Ud#BMP
m!!)!('#LB&"#TdKk!B"1V3)56Ud"LLmX!!B[,!!+3QG#TdKk!8K1V3)56VVc%$P
m!!-!(%*R,b`!(NkY!$T#Tbm-6VS)D#eIrq![#dkY![*6EHlNB!!"#!aX!!B!('B
!!-S`,!!38N!j3!!3%!4R!!##$'`!"3!N9m"+,!"19X(!!@Fm3QF[$%*R6VV[j"!
I#J!!!@FL2c`!!5mX!"j1V3!k3UF[$%kk#!3YArrJ,`Y1V3,bB!!!U%)%B!$pKQ!
b3QF[,!!B6Ud#5KeIrmdr2!!",b`!(NkY!$T#Tbm-6VS(c#eIrq![#dkY![*6EHl
NB'`YE[rUrqiT5`!+,8[rkLCZrqj#Cbm-2`G1ZJL%29rrj!b(!!!#!&h!$%Erf9I
"J!&R!RJ"B!$p'N*R,b`!'%kY!NSGArr03QF[,!!H6Ud!1N+R,`a1ZJGN,9rri#m
,6Ud#mP0YlZ4J"'!!r1C1V3)L60mBm%jH,Tp1G4p5CA4bH5"XD@eTG#"PH'0PC@4
PC#`JCfPfD@jR)(9`"&4'9&!!#fCTE'`JF'&MDf9d6PB!!#m-+'i!#$!X!$*53$P
!!$)`,!!L8d!j3!!L-#`!0&*!18!!0$!X!$j53$P!!$ijI!!%!"`[,!!86Ud#-LK
I6PiZRdje6PErr%MR!3JSEJ!)3UHTG5iIRU`!4JaX!!%!3'B'1A`!$!"#$'`!!3!
qCKBJ,!!fd)F[!(!#,`"1V3*U+9m!0Q!i$'`!!3"!AX!-E!!"!%*H`F!"C`S`,!"
#8d!j3!"#,b`!0M!X!%*)`#m!6Ud#DL!Id+`!0LP!!$B[,!!fF!-[!%kY!RSTA`!
k$+`!!!0J!$T[##Pm!!!$B!!k$+`!!!*B!$TX##Pm!!!#@!!k1@`!2J"!60m3J%j
H,Tp1G8j@rpK)j`mB+'i!$LCZ!!Sk,J!)6Ud!BJa&!!4X)#mX!!B[#d*R3UG)HJ,
k6Ud#%Nkk#3`[$%kkmLKJ!!+H$'`!"!!FCJJjI!!"!"aJ)JaX!!%!('FD,b`!"Lm
,3QG#TdKk!U*1V3)56VS)e'!!!Qa#Tbm,6VVUC#eIrrKC45"ZrrJ`+!!#X'`!%'F
1-#`!-&*!18!!-'!!!N*#CbmX!"K1V3*+(9rrlbm-6VVqPLm-)'lrq$mS!!*1ZJ+
!5-8J"G#X!#JT3!!S3UF[#dkkmF)YArrm-#`!*'XBX(`!"fi53IS#)M)!jNP%33%
`%2m+2!!%CJ!"SK!X!%j)J'F18d"R#P0!C`!!q'!!!9B-E!!"!#4R,L"X!%SKE[r
m!#")a5"X!%SK43!N)'`!5N*S!#a#CbmX!%T#CdkY!H)m(f!!!5!3,!"%C`T#,!"
%H!&q!@!%3N4#4b"X!%T#D!!X3NDk4fm!!*K)ab!(d+lrr#e!rr!JE[r`$"!!$9E
!Ca#k4el"`!&R#&*(8Ulrm'$NZNGQ#L!&8d!q!&1Zrr!J"j!!4&*!5-!JE!"+)8!
!*%M%)!63V[rm)'`!5L&!!#"#CbmX!%T#CdkY!H)m(dT'C`*J,#!&8d#`4eI!CKB
JE[r`$"!!$9I"`!&R#"Pm!!%!4'"`)!G83$J!2J4J!2pQB'*#Tbm,6VV`M#eIrqK
#CdKX!&)r,!"35'lrf%kY!1Sm(b"ZrqJY82rB)'lrk#eS!!6rh%+R,ccrrrlr)'l
rk$!S!!K)`#m!U&JJ(ce!rq"#CdKX!&)r,!"35'lrf%kY!I)m(dT'Cc![,!!',`X
r2!!$3UG)HJ#'6Ud#%Nkk"X4#TdKX!&*1V3)52`C1V3$D,`a1ZZr3B%CJ(LmX!!B
[#d*R3UG)HJ"'6Ud#%Nkk"T3[$%kkll"J*Ja&!J"Q#$Pm!!F!('!'1A`!#!!F-#`
!%&*!18!!%#mX!"41V3)b60mBm%jH)&rHr!!+6Y!25@jdCA*ZB@`J4A*bEh)Z#84
TFfXJ4R9XE!!f(P*PBf9TGQ9N)(9ZCAK`C@0dC@3JC'&dB5"LE'pMD`!9@@pe)'K
KGQ8J3e05)'4TFf9KFf8Z6PErqNMR!4JQEJ!+3UF[+`!+6VVRM#KI0h`!"!!11,`
!"$PZ!!J!!N*R,`Xr2!!%6VS!%$iI60mBJ%jH)&pF6dl36PErr%MR!"JSEJ!+3UF
[,!!+6VVR5LCI$&-!!9E!$&-!!PE"`!&R#$Pm!!S!)Q!'1A`!"!!L0T-jEJ!)!!i
`,!!Z8N!j3!!Z3QF[,!!',b`!#MmZ!!K1V3-55PpZ$Memrrm!$MPm!!8!('!Q,b`
!1N(kq``[##m-,b`!'%kY!Q*#TkPe+9m!4MPm!!%!2N*Z!!j-haJ!6PiJAea26Y"
19[rk51F"#%+R,c`!!!&56Ud"JLKI)!aQ)%+R5(S"T%kY!K*#TdKk!E41V3)56Ud
!8N+Z!!aJ!!&`3U`!"N*X!#a#E!!Z3Q`!-%*X!$*#E!!d1@i!#J!Q1@i!#!!N3U`
!+$Pm!!`!3R!N+8!!0N*X!&"#,!"%,b`!0R!$,`"1V3*k+9m!1JbX!!!$B!!kE`J
TI!!!!f!!1JbX!!!#@!!kE!JTI!!!!PJ!1N*X!$ijI!!"!%!jI!!%!#*#TdkY!N)
TA`!B5U`!''BQ3UG)HJ$d6Ud#%N+R5(S!rNkY!K*1V3"5,`a1V3##3Ui!$'!!!,T
#Tcmm!J"#CdkY!Y)TA`!+5U`!#QB`3UG)HJ#i6Ud#%N+R5(S!Y%kY!K*1V3"53QF
[,!!B6Ud#8KiI,`a1V3##3Ui!$'"b3UG`8#m!6Ud"JLPI!%T+V!"+CMK#TdKk!(4
1V3)53UG)HJ"D6Ud#%NkY!&*#CbmX!"K1V3*5(Km[,!!+6Ud#mLm-6Ud!JN+Z!!a
J*L"X!%SaI2rr!"JTEI2m!"4#P%)X!!3[$%(YlXJ[#%kY!)SY6!!-60m3J%jH,Tp
1G3p`BA*KE@9dCA)JBQa[BfX%9%C88!!0Eh9dF(9d)("KBfYPG!9dD@ePFK"MEfj
ZC@0dD@pZ)'*XEf0V!%j@rrK)j`F)+'i!#%kY!MS-E!!$!#4R,N*R,b`!5N*R6Ud
"NMiI)'`!5N+S!")JE!"+-@`!8!!@3QF[,!"+3QG1V3'U2Km-E!!+!#CA`$)X!#4
V',*m!!GZ%N(k!)Jd!HC+4%)$-#$r#M`!"&I"`!&R%N+R5'`!8NkY!K)r,!"36VV
T3#mX!!C1V3,L3QF[,!!B6Ud#5K`I3QF[,!!B6Ud#8K`I,b`!5NkY!))[,!!+6Ud
#mMPm!!-!(#SX!#K#Cbm-3HhZb#m)6Ud!HMiI,`a1V3##,88!$%cI%1"1ALkI6R8
!&%j@rra)j`!B*Qi!#N+R,bX!#NkkipSSAcLm!!-jD`!3!!*#Cbm,-#i!#&K!2`"
1Z[aJ29m!$NcI'!"1AL"IA%p1d%j@rrT)j`%B+'i!$N+R,bi!#NkkijJQAc!V!!+
`E!!3C```,!!`8N!j3!!`B#![$%kkpq*#CbmX!"K1V3*+(KmjI!!'!"`[,!!86Ud
#-NcI')"1AL"Ih[`!#Nl36PErq%MR"aJSEJ!)*Qi!%MSZ!""1V3"L-#`!,&*!18!
!,%+R,`Y1ZZ-S,KmJ4c`3)%F`KM!'9d"R$&0!Cb463'FkB!!!KN*R,``[#dkk!+S
3(fF+,``[#cm&6VVi'Q!!!)*#Cbm-,`Y1ZJ#1%"pR#Lm-,`Xr"8kkrbaJCNTX!"*
A`#"X!!C)ji#!3UG)ji#!3UF[#dkY!6)L(dcI!3%[!8kY![SL(dcI!3%L36)S!!D
b89I"J!&R%#m-,`Xr"8kk!9B[$%kkkK"J'#mX!!B[#cmm!!4#TdKk!"j1V3)56VS
!e#m,6Ud#mNcI'1"1AL"Ih[`!$Nl3!5"19[rd51F$'#KZ!!`X,J!)3UF["NkkiN)
QAcGV!!)!!N+R3UF["NkY!6*1V3,k)&mq%%TX!"*Q1$!X!"#`D`!#CK!jI!!"!")
JE!!'-8F!"Q"',b`!"Lm'3QG#TdKk!%j1V3)56VS!9N)Z!""J-'!S)'`!"VjS!!C
R(LmX!!B["Mmm!!9#TdKk!#*1V3)56VS!,%)Z!""J"Kem!!%!%%cI'-"1AL"I8%p
1d!%J$QpXC#"MEfjZC@0dD@pZ!%j@rrC)j`-B+'i!%LCZ!!iq,!!',#`!#%+R3UF
[#dkY!6*1V3,k)&mj8!!'3UF[#dkY!6)JAbPS!!`!##m-,`Xr,J!-,bi!#%kkja!
j4`!'+8B!#%cI'-"1AL"Ih[`!$Nl36PEqq%MR!3K#TbmZ!!T1ZZ%U+&mJ$&L!,`"
"l[lm,`K1ZZ&m%#lqr!*!!2mp32liIJ&J1%(Zr[`3-(!!!N!!r`a!!!eA`%(Zr[`
5-(!!!N%!r`a"!!TA`B!"C``J"d(Zr[`4[!!J!!"54fN'[Qlqq'r#3UG)HJ!S6Ud
#%N(Zr[`[#%kY!*SJEJ!1-A`!"3!F60m3J%jH)&rHr!!+6Y!I9%C88$SJ4A*bEh)
JCR*[E5"QEh*PD@GZ)'K[Fh3k)!!!&A`$1!!,6PB!!#m-+'i!%(!-`Hi!$PC!1)!
`,I4i`Hi!$&C!18!!!R!-`Hi!#Y"818!!"$!Yp(M"lJ!)d'`!!MP!!!BSAdjH)&r
Hr!!-6Y$038Y&8N9$9!!!6PErk%TYp#*Q!Q"%5'lrq$mYp)Jr,I3N2c`!!6mYp#*
1Z[q+%#hdI@F-5'lrq%KYlUUST@!'5'lrq+LM3Hhd*Lm)3QFr,I3LU)9#EI3LU&C
1ANjeaNa98dK#98B!!%j@!!"+EI3LCJBlEI5'p#3`,I3L3E`!8%(Yp#B4VJ!*!!"
5EI3L6PiJAe426Y$#98CI3dK"8J!!6PErqNMR!`!q,I5+-#i!#&0!28$rqN*'B"4
2[!!A)!G"lI`Qid!q-!!!8NCT"VaZrrT[jMe(!!T-h`$!6PiJAe426Y$'6N4548`
J)!!!6PErrLm(2LhdLNqm!"FJ"d(Yr#EM3$)`!!#bEJ!)Ca*2[!!A)!G"lI`Qid!
q-!!!B0Jp4`!+,Kp1AL"I9%p1d-C14&"59L!J!!"19J!!-#hdH-(Z!!Sr!(!-`Hi
!#$m!U*3`,J!)d@hdL$!Z!!V4EI5'3QFr,I5)6VVr1$YIp)41ALkI6RA548a06eC
&)!!!6PB!!$!Yp(M"lJ!+9N!r!$!Z!!K53-(m!!a@3&G!2`#SNcYZ!!MdL$YZ!!V
dKN*R2bi!#%kkrZ`lAr5%6PiZRdje`8*668p@45!!!%j@rri["d*(B#!`,J!)3E`
!&d(Yp)c"r!"36l`!6b)(dN!4[!!J%!"54`a(!%p[fLiI6PiJAe426Y$D49*26%P
143!!6PB!!$!Yp(K%3$m!3QHSNN)Yp!e1ANje`e958dp5Ad8!!%j@!!!r,I4i3QH
SNKYm!!(d$8jH6RA$99*66e*I4!!!6PB!!%TYp)C[#Mmmrrp#CdkkrYC1ANje`N&
$5ep68%%!!%j@!!"#CcmYp)K1Z[m!6Pj1GF0"8P**38G&!!"19[rk51F(!$!Yp)5
`EI`JCJ!!`%KYr&a#Ccmm!!`[,I3HU1mr,I`L6VVr%M`Yr#)q,I`J-#hm)N'm!"G
"lI`Qid!k-!!!-#hm)N'm!"G"lI`Qid!aVI`J!!!lEI`Lr#"#CcmYr#*1Z[hi1er
m)VjYp)TQ#$YYr#$dLQ!D3QFr"dkkrGi`(d'm!"G"lI`Qid!aVI`J!!#mEI`NCK`
lEI`Lr#3`,I`N3E`!&d(Yr#EM3$'mrrm!!'!5-#hm)N'm!"G"lI`Qid!aK3!!1fh
m)25%B!T#Ccmmrrp1Z[h360m!i%jH6RA549CI6%P143!!6PErqNMR"`!`,I5%X'h
m)QB!!-C)EIaF3QFr22rd,bhd(UM[2bhm)%kkrL3m,I`L2Lhm)$!Yr#*"[!!A3Hh
m*Z0!1M!!!$!Yr#*"[!!A3Hhm*Z0!-Dhm)!!!1fhm)2`L-#hm)%'m!"G"lI`Qid!
lF!!!r##qEI5+CJJlEI`Jp)TJ'N*R2`G1Z[cU-"p"[!!A3Hhm*Z0!-Dhm)!!!['h
m*'BF1fhm)[`N-#hm*%'m!"G"lI`Qid!a[2rr!!"J%M!Yr#*"[!!A3Hhm*Z0!-B8
!!$YYr#,dK'!+3QFr2!!"6VVmh%cI!1"1ANjec%P149p'488!!%j@rr4)ja%!5'l
rpMmYp)Jr,I5'2c`!!A"3N!"Yp)Br!%kkqb4)E[rf2bhdH%*R,bhd(UM[-#hdKP4
!28$rp(j2B%)`,I5)3E`!&d(Yp)c"r!"3)JG6381m!%md,I5)4E`!&d2Yp)c&r!"
36l`!6bB(eN)8-6!!!N)!rp*!%B)3!&0(D3DqE[rdE,J`,I5)3E`!&d(Yp)c"r!"
3-LhdKN1m!%r53"'m!#!3!%cI!)K1ANjeb8j649*8Ad-!!%j@rrJ`,I5%3E`!&d(
Yp)c"r!"3-LhdKN1m!%r53"'m!#!3!%KZrrJr,I5)2bhdKMmm!!%r2!!"6VVk8"!
Yp(eR$%KZrrK)EHkUU+9J"NKZrrLSSdjH6RA&8N&649p$5!!!6PErrLm(3NGJ-Nq
m!!JJ"d(Yr!lM3$)`!!#bEI5'EaT2[!!))!G"lI`1id!r-!!!2bhdL%kkqlTJ#&*
($%F!#'r),Kp1ANjee%&#)*!&!!"19J!!2c`!!kR)6Pj1GF*&6%`JN!3!!%j@!!!
lEI5'p))lEI5)p)"1ANje`e958dp5Ae-!!%j@!!!r,I5#2bhdJ%kkqeT1ANje`e9
58dp5Ae)!!%j@rrj1ANjed99&8PPI9%8!!%j@!!!-E3!rp(T@`'F5$'d!"23D9m(
!!@F''h`!!I`-6Pj1GFP18d959&p0!!"19J!!$'d!2r4k9X"R%!aY!!6d'PI"`!&
R"%)Yr!a1ANjea8j%AdP18d8!!%j@!!"+EI3DCJBlI!!"p"T#Cc!Yp"T%3$m!6VV
kI%jH6RA98##3"J!!6PB!!%TYp"TQ"MYm!!(d'N*R2bhd'NkkqP41ANjea%pA6L#
3"!!!6PB!!%TYp"TQ"MYm!!(d'M!Yp"T%3$m!3QG1Z[SS6Pj1GFa&4P3JN!3!!%j
@!!"+EI3DCJBlI!!"p"Sr,I3D3QG1Z[S!6Pj1GG**4dK8)*!$!!"19J!!8fhd'NT
Yp"TX"%*Yp"T6EI3F5Qhd('`%3Qhd($mYp"`r,I3D6VVk$%jH6RA$99*66e*I8!!
!6PErpLm(5'lrpN*R3QFr2!!B2c`!8%kkq"a)E[rfU+0#4f!)2`G1Z[SJ8NF-4`!
AEr)Z(djH6RA$6%9"8Pp63`!!6PErp#m(-#hd'QF18d"R9&0!C`!!RQ!!!,K)E[r
f2bhdL$mYp)Br2!!"F&#3!'hdKMm!6VVh[MiYp)CJ)$!Yp)4"[!!A3HhdM-(m!&"
2[!"2)JI53"'m!#!3!&*($%F!6frDB'a)E[rf2bhdL%*R2c`!!6!Yp)C53$m!6VV
hGMeYp)Erp%*(B#)`,I5%3E`!&d(Yp)c"r!"36l`!6b)(dN!4[!!J%!"54fN'[Ql
rp'rBB"j)E[rf2bhdL%*R2c`!!6mm!&"1Z[FX2bhdK%kkq6K)E[rfU+-Z(djH6RA
$6%9"8Pp-53!!6PErp#m(-#hd'QF18d"R8P0!C`!!MQ!!!)j1Z[lq5'lrpM!Yp)K
53$m!3QG`'*!!EI5)2`!r2!"36VVfdNKZrrDSSc!Yp)K53$i!B!j#Ccm(6VVhT%k
kq-T54`a(!"G[l'"%6VVqYNKZrrC#Cd*R2bhdL$mm!&"1Z[D85'lrpULM2@hdL2r
d3NGJ%%*R2`G1Z[GQ6VViM&*(D3DqE[rdEqTJ"%kkrM3Z(djH6RA&8N&649p%53!
!6PErp%MR%3"+EI3DCJBlI!!"p"T)E[rf2bhdL$mYp)Br2!!"F&#3!'hdKMm!6VV
f+("3N!"Yp)C63,"Yp"TX$("3N!"Yp)C63$Y!p"T)E[rf-#hd'X(Yp(K%3$m!3QF
[,I3HU1p`8*!!EI3D8d!p32rd2LhdKQ"%-#hdL%'m!"G"lI5-`I`!8%qm!%mL"c3
Yp)K&[!!A3qhdM-Am!&!f,I3DeNG([!"2eN)8-6!!!N)!rp*!%B)3!&*(D3DqE[r
dElB-4`"3E#)`,I5)3E`!&d(Yp)c"r!"36l`!6b)(dN!4[!!J%!"54f$B60m!L%j
H6RA%48a&9%9I3`!!6PB!!$!Yp"TR*P0!CcTA3'GJ9d"R!!#'"%!!$fF!!)j93'F
!!0"A3'F!!4*J!!%B3LhdI8)Yp(`r1J%DU)Jr2!!"U)PJ!!%!F!'!,I4m'd$dI%*
!%#hdI$m!2c`!!Mmm!!*1V3++2c`!!P52U)KJ!!$@F!5!,I4m'd$dI%*!%#hdI$m
!2c`!!Mmm!!*1V3++2c`!!P52U)KJ!!#X'h`!!I4p2c`!!kL*B!!!R(!!DaL`I!!
(EK*"lI4p-J$Q584"!6!3r`Sm!!4Q+(!"4J$!,I4m'd$dI%*!%#hdI$m!2c`!!Mm
m!!*1V3++2c`!!P52U)KJ8R!#DaL`I!!(EK*"lI4p-J$Q584"!6!3r`Sm!!4Q+(!
%4J$!,I4m'd$dI%*!%#hdI$m!2c`!!Mmm!!*1V3++2c`!!P52U)KJ#N)Yp(dr2!!
"U)P1ANjee%9B9&p06d3!!J!!6PB!!%*R3QG1Z[A'6Pj1GFK2689I3e95!!"19J!
!8fhd'NTYp"TX"%*Yp"T+EI3FCJBlI!!Bp"a`$-(Yp"T@3$Y!r&a`$-(Yp"a@3$Y
!r'!lEIaJr&C#CcmYp"T1Z[581erm)%*R-#hd(&0!2`"1Z[5#1erm)NkkriK1ANj
edd98Ae0$8Nm!!%j@!!!`,J!)Ad"R)&0!Cb*63'FN8d"R*P0!Cb*63'FH8d"R)!4
!!!jR)'!N6VVjI'!H6VVejQ!B6VVj(Q!56VVh"Q!-6VVepQ!'1h`!!I4q6PiJAe4
26Y$%6ep$9&*-)!!!6PB!!%)Yp!j#,I323Lhd&%)Yp"9"lI31+dMd#(!!3E`!rcY
!p(SlI!!#p(j1ANjec99-9%PI3dJ!!%j@!!!`,J!)"%!!)fFS@d"R*&0!Cb!%3!!
1Cb*63'FN"%!!$@FN88"R*J4!!!eR*P0!CbKJ+MYm!!2dIQ!L6VVijQ!F6VVirQ!
@6VVfAQ!36VVeDQ!+6VVj#Q!%6VVrD%jH)&p86dl3a%pI8d963b!!!%j@!!!`,J!
)"%!!3@Fi8d"R1P0!Cca63'Fq@8"R3&9!Cd*63'G%@d"R4J4!!"0R4PG!CdK93'G
+@8"R6&0!CdjE3'G3B&*1Z[N8B%a1Z[NkB%C1Z[Q)B%"1Z[P@B$T1Z[QNB$41Z[X
!B#j1Z[S8B#K1Z[ZUB#*1Z[KiB"a1Z[Q'B"C1Z[KqB""1Z[LQB!T1Z[b%B!41Z[h
Z6PiJAe426Y$%6ep0490$)!!!6PErpNMR!`!JEJ!)3qlrqL,B-T!!3NC#4dqm!!9
+0R$kCeT2[!!&%$C`qNL!$%!!-&c!E5j2[!!&%MC`qNL"$%%!19r"`!&R'L!'`I`
!#Nqm!!850R$k5)'5I!!`dN!m!@!@6l`!"4!fF2T)J!a!!#eQ"L!'4%!m!&*(B*`
p4J!-60m!`%jH,Tp1GG088Pp86ep*!!"19J!!,`Fq,J!)$%F!)'`-6VVaGMm(6VV
pR'"X$%F!J'aQ$'d!825'E5)3,I`0C`j1Z[&@6VVcZNkkp-"J$Nkkmij+EI3LE`4
6EI3L%#hm$'F16VVeQNkkpP)r"kL$B!Br"dkkmB)`,I5%3E`!&d(Yp)c"r!"3-Lh
dKN1m!%r53"'(%!"5EI5',Kp1AL"I9%p1d-e%8N&A3dK"!!"19J!!,`Fq,J!)3UG
)abm(F(m[!+KB)"p"[!$r2J!`,I4qCa*63'FB8d"R)P0!C`!!`Q!!!-)r"dkkrbT
J!!#i3QhdIMm(6VVpE'!!!+S-4`!JA-"YG!a(!%"G`F!"CfS-4`!mA-"Y%!a(!$p
I`F!"C`Bl4r4kB(i-4`!`A-"Y#Ja(!$PI`F!"CK3-4`!Y9m'!!@B+$%F!+eI"J!&
R($!("%$rJ%'m!2mJEI3)%)G5VI3))'hd#%)3B$`-4`!lCJK"lI38+dMd#'!X3QG
)EI316VVprMYIp"T#CdKYp"41Z[h`1erd($m(6VVp2%*Yp(jJ"%*Yp(iZ(djH)&p
86dl3d&**6P4*9#!!!%j@!!""lI31+dMd#$mm!!QSLMmm!!'SL8)Yp(a#3"!Yp(`
r!$mm!!)r2!!#6Ud#LMmm!!*8MkL)3Qhd)N)Yp(e#,I`-3UHTG5YIr&K#EI4q1h`
!!raH1h`"iraL1h`!!raFF!c"r!!B9N!l32aJ1fhmB2a@1fhdL[`J1fhm*2`L6Pj
1GF90Ae*&8d98!!"19[rD51F$!%Kk!@T)E[rmU3!pI!!Srr3pI!!%rrBpI!&8rrJ
pI!(mrrT#Td+R5'lrp%Kk!6*)HJ%N5'lrfMmm!!*1V3+53HlrfKm32c`!rdkY!TS
[#"mm!!%r2!!%F2m[!%*R3UHT%bYIr'3[,IaNU(-r,[rmU)Fr2!!*U)Sr2!!"U)P
#,I4m3N!3,I4m2`!r2!!#2c`!!NkY!SSr2!!#9)qSL%+RU0JVAr3H2c`!#ULF3QF
r2!"AU)dlAr4i3QhdLMYm!"Im*%*'B"BJ"P*!6E`!&b)'3Hhm*Z0"-B!3!&*'$%B
!&frN-#hm*%'m!"G"lI`Qid!a[2rr!!!EI!!"r!e1Z[jZ3NGJ'#!(8N$R3%qm!!J
L"d(Yr!lM36'!%!"54`a(!!K[iN*(B!Jr"dkklq454`a(!"G[mNkkpC!!6VVjZ%k
kmrj#,I3060m!`%jH6RA&69p*6NP8)!!J#6)a,8j[GLdi03e0B@0848a1493JEfB
J"Qe[EQ&ME`"19J!!%#hd$@F%6VV[d$mZ!!K1Z[cB6PiJAe426Y$&65#3"J!!6PE
qr#m()'i!#%2Zr`"`3#,B8d"ZqK!Yp!eR"%kkljB3,[m!!N!!rce!r[aq!@!D6l`
!rd(Zr`!3-(!!!N!!rcm!6VVmJ&*(D3DqE[lmEq!Z(djH,Tp1GF908e45)*!$!!"
19J!!%#hd$@F%6VV[4Nkkl6j1ANjea8eI4Na98dJ!!%j@r`!JEJ!)3qlr!("!)YK
63'lk3Hlr!"m32c`!rdkY!TS[#%kkreK1ZZd%6VV[D%kkm'j1ALkI6RA&68a1)*!
%!!"19[rk51F"'#KZ!!JQEJ!-1,`"@#mV!!)[2!#3!i"1V3*b)"p"[!$r2J"#Tc!
V!!j)`#m!,c`!!!%!U&K+ReE!CcS-4`"!A-(!!@F`$%F!B9c!E4B-4`"kAm(!!@F
-)!H3!(`!)%'m!2mq!(!")JH5I!"!3l`!raQ"!!"JE!a(!'"Q,%+R-#X!$NM!,`!
[2!!!!3#S@%UICJj`!A)E3l`!raQ"!!"J3R!"'BF!!'!k$%F!)'BZ3UF`+`!15-!
[!#mm!!!"!+KB5TpQ#R!"'E`!)!!!B"4`!A)!3l`!raQ"!!"J"R!"'BF!!%cI')"
1AL"I8%p1d-90AdP18&98!!"19[rm51F$!#mYr'5T)MiYp)T#4Q"#2c`!!b!'8N$
"r!!-9N"A3$m!U*02[!!A)!G"lI5-`I`!8%(`!!![#%*R2c`!8+L&6l`!&b!(3Hh
m*Z0!2M!!!&*'$%B!&fqi-#hdH-(Yp)C@3$m!-#hdL&*!`I`!$&C!9d!r!+L66VV
YP#mYr'5T)dcI!-"1ANjeb8p98%4"9%8!!%j@!!"1ZZY13UHTG5!IN!#Yr&J[!%+
R,VJ#p#)I)"qbJ'iF%#hd$3S!!!&R"Nkkl8aJ"%kkl54#TkPe+erm@%jH6RA*6dP
%6%8J)!!!6PB!!%jH)&p86dl3b8p"3e4*9N%!!%j@!!"1AL"I8%p1d-P23da*3dX
J!*$r!*$B(!(+!!P86%j8!*!$8P0*@N8!N!0H4P*&4J#3!fT#6N4-!*!$GN&-8P3
!!3##4%P86!!'!*T%6%p(!!3!lNe&6P8!!J%U5801)`!!!8j$6d4&!!B"@J!!rrm
!N!MrN!3!N!-U!*!&J2rr!*!$1!#3"B$rr`#3!d-!N!3"6Irr!*!$B`#3"!+Drrm
!N!0c!*!&$Irr!*!$J`#3"62rr`!!!6-!N!8qrrm!!!&r!*!%!Ecrr`!!!Km!N!3
$#Irr!!!#J`#3"8lrr`!!!cX!N!9Hrrm!!!94!*!&#rrr!!!'43#3"6(rr`!!"R3
!N!8Irrm!!!DA!*!&6Irr!!!'`J#3"9hrr`!!"ZN!N!3"!2rr!!!(%!#3"!%"rrm
!!!Gp!*!%!3,rr`!!"qN!N!@!rrm!!!Kb!*!'rrmJ!!Pf!*!&!Irr0!!0'J#3"3,
rrc!!BZi!N!8$rrm`!'a-!*!&"2rr-!"`A!#3"3Arrc!!GMJ!N!8'rrm`!*A%!*!
kEK`:
!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.

tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)

echo extracting tftp.Hqx...
cat >tftp.Hqx <<'!E!O!F!'
(This file must be converted with BinHex 4.0)

:"(4QG(!!39"36&4'9&!J!*!(V!$cG3#3"!%!N!1S!*!$T`#3!`)G!*$c*#084P4
3)&CPFR0TEfiJ-Lib,#!a-#"6CA"dC@eLCA)J-6Ni03#3!`G"8&"-!*!'(&4'9&!
!N!-"5801)`#3"B"'8N9'!*!&J!#3!``!8!"N!2m"T!'mrrm!N!--!&!!C!$r!D3
$#Irr!*!$m!!*!*!&FJ!2!)3!S!3#6dX!N!9b!,i!K!&2"!C$B@jMC@`!N!8L!!m
!-J&2N!!08(9d)%jKE@8J5'9bC3#3"P3!$`"N!8q3!!e3GA3J5'pcG#")CA*P!*!
'PJ!2!+8!@JB&390$58N!N!D@!&m!T3#U"J9*E@&RC3#3"TB!V`#P!2S'"8pMG'9
d!*!'PJ$r!+8"5JB$6@&M!*!'$!!2!"`"6iJ$AM!k!*!'2J!2!%i"6iJM6Q&YC5"
[FL""C'4bCA0c)'pQ)(4SC5"5C@e[G'8J5'pcG$S!N!4q!!3!N!9"!#)!B3"b"!*
25`#3"8%!Y!"K!33%"N0"6N0&6!#3"5-!)`!d!(-&"8CPG'0S!*!')`#d!$-"'!8
&8h4[FQ8!N!B+!#-!(`%BL#**EQ4TBf&dC5"hD'PMD#"dFQ&ZFfCPFR-JG'mJB@a
XEhFk!*!$5!!"!*!&#J!+!"N!m)JC35"1CA4hEh*V)%9bFQpb)%KKF("PEQ9N)3#
3"P!!#J#(!2#)$e0PG#"LH5"6CA4*9'9iG!#3"(!!!`#3"3S!A`!H!*X%!Np,!*!
&#J#j!"i!p33'3d&13d9-!*!&33!+!&!"5T!!!f4TFJ#3"LJ!#J!h!8U)+P"XC@&
cC5"dHA"P)'PZ)(4SC5"QEh*PD@GZ)'4TFQ9MG'pbH5"ZB@eP,J#3!rS!#`#3"4`
!SJ!Z!2)%"L!J6h"PEJ#3"6X'%!"0"0#%#b!J5@jfDA0TBQaP!*!'@J#L!'`!mJ3
))#"$B@jMC@`!N!8@!3)!0J&LJ!#3"MX"#J"0!9S%"b!J4@TPBh3!N!CD!3S!E!&
D"!FJ)%4bDACP!*!'#`!-!(d!K`#3"`X!KJ"p!*B!N!F8!2i!G!$rJ!#3"K3%&!"
d"(Q)$NPZGQPcD@*XC5"8CAKd!*!&I!!-!)`!K`#3"hd!V!#-!9S&&deeE(4TF'a
P)%CTE'8J8f9XC@0dD@pZ!*!%R!!$!*!&#J!b!#J!EJ3#6dX!N!8+!)`!+!$)"!C
$38j$48`!N!9c!!S!JJ$`N!!54'9QBA9XG%C[FQ9TCfj)Eh0d!*!&-J!+!'3!m)K
)8'aPBA0P)(4jF'8JD@iJG'KP)'jKE@8JEh)JER9YBQ9b)'pQ)(4SC5"QEh*PD@G
Z)'K[Fh3JH@pe)(GKER3JG'mJFQ9KBfJZ!*!$B!!#!*!&M!"Z!+8!dJ3%899*9!#
3"3S!@J!h!5b),N%JCQ&dB@`JCA*bEh)JD'&`F'9ZC@3K)#"8D'8JF(*[Ch*KE5"
hD@aX)'4TC5i!N!93!"3!K`%XL!*H-!#3!l3!"!#3"B`!EJ#P!0)%"%K"6&3!N!8
+!&S!0`%XL#P'BA4KE#"&FR*[FL%J35"dBA0V)'pfCA*QE'phC@3JDA4c)(0dB@0
V)3#3"P!!&!"I!5b)&P0dB@0V)&"[D@jdCA)JB@*[GA3JAM!!N!9N!"3!F`%XL"K
8BA0V)'0[ER4bEf`JBQa[BfXJBA3JAM%!N!9i!"3!K`%XL!p8BA0V)'jKE@8JDA-
JAM)!N!-#%J!9!*!&$`#d!"i"#BJ&F(*PBhB!N!BM!,3!-J%*L!4`Ffjd!*!&0`#
d!%B"#BJ&F'4bEh!!N!C,!,3!@J%*L!9LC'0SD`#3"Pm!Y!"Z!3Q)"R9ZF(*[G!#
3"A-!Y!##!3Q)"Q*NGQ9bF`#3"BF!Y!#@!3Q)"@*NE'9Z!*!'Q`#d!+S"#BJ'G(4
XCAK`!*!&V`#d!,i"#BJ&CR*KCh-!N!E$!,3!dJ%*L!4QFQ9P!*!&e`#d!1B"#BJ
%CQ&TE!#3"3m!#J!H!+U)%9"KBfYPG(-J8Q9MC@PfC@3k!*!')`!+!$)!USJ08'&
MDf9dFb"6C@jd1J#3"MF!#J"'!+U)%&"KBfYPG(-J4(*[F("PC$S!N!9,!!S!@J#
UL!j#B@3J3fKPBfYcG@ec1J#3"9m!#J"Z!+U)&&9ZD'&ZC'aPC#"3FQpdEf0[E(-
k!*!&F`!+!))!USJ03Q&N)&CPFR0TEfjc1J#3"SF!#J#@!+U)$%*KC#"-C@jRG'K
c1J#3"CX!#J#U!+U)$&486#"&H("TFQ9N1J#3"Dm!#J#q!+U)#NCbB@GYC@jdFcS
!N!A$!!S!dJ#UL!e'FQ9P)&"KBfYPG(-k!*!'e`!+!1B!USJ36'PcG'9Z)%CKD@a
eFQ9c1J#3!r!!$J#3"3S!#J!C!%D)"8a[Bf&X!*!'#J"1!"N!LSJ(4QpbC@PREJ#
3"JS!NJ!C!-k)"%K[Fh3!N!8+!0B!'3%8L!G)B@jNE'9b!*!'#J%F!"N"@)J#3fi
!N!8H!!S!,3"'L!#3"Ki!6J!Y!)U)!*!'(J#5!#d!cSJ!N!BH!0B!,3%8L!#3"Ki
"(!!Y!9L)!*!'-J!+!%%!4SJ!N!Bb!%i!33#+L!#3"M)!NJ""!-k)!*!'-J$@!%%
"&)J!N!Bb!4`!33&BL!#3""8!@J",!33"U3!"!3#3#"%!N!39!'3!C!$A!C!!!!)
"!*!)0!#3""m!C!"p!28"G`!$!*!*-`P&FR*[FL"#EhJJ!*!$*`"N!&!![J'N!!%
"!*!)24&'Eh*PD@GZ)%4TFQ9MG'pbH5!!N!-9!*!&Q!&Q!!%!N!M`A`#3"#F!C!"
p!28"G`!"!3#3#$i46Q&YC5"'Eh*PD@GZ)%K[Fh3J!*!$)`!b!'3"*`&h!!3"!*!
)6Je*8#"6G'&dDA0dD@0c)!#3!b-!C!",!2S"U3!%!3#3#&i19843)&0dBA4TFh4
TBh-!N!0,!3#3#Iq3"!4'D@aP"NGPG#k3!`#3"!C3GA3ZN!-!N!3*8f9bGQ9b,T!
$!*!%"8&LEh*d!*!%"&&eDA3!N!L$!3%!N!MrN!3)8f9dG'PZCh-+390$58NJ6@p
NC3#3"!T*68&(45"0Ef4P!*!%#Np$9%98)%e[C'8!N!3168&$58j86e0))%e[C'8
!N!318Q9YEh4P)%K[Fh3ZN!-!N!368Q9YEh4P)%4TFQ9MG'pbH5k3!`#3#'S"!J#
3#2q3"!46D'ph$P9%8#"6G'&dDA0dD@0c!*!%%dej)%PZG'9bEQ9d)%&NC(*PFh-
!N!386ANJ3A"`E'9dB@aV)%&NC(*PFh-!N!3059!J8h4KG'PcG'PMF`#3"`%!N!9
rq"rq3!J3!Nr)%r*3+"3+8#J8#P!S&!T2b"2b3!J3!N(S%(T!$r!#IrJIrL!3#!3
J%!J%2r!2r!#3#(rN"rj!+!3#AlrpqN!S"!*2T!Ab3#!%!PmJ"2T!)#3#Ak!9qN!
rr!*2S"Ab3#!N!RrJ"ri!N!arq"rqIrrrrRrrrrjrrrrqIrrrrRrrrrjrrrrqIrr
rrRrrrrjrrrrqIrrrrMrrrr`rrrrm2rrrr"rrrrJIrrriIrrrrRrrrrjrrrrqIrr
rrRrrrrjrrrrqIrrrrRrrrrjrrrrqIrrrrRrrrrjrrrrqIq!(rJ#3#L+f6[S2&%l
k%AK1qKK`6[S4l%lk%a!JE`!%,Tp1d%j@rmC)j`mB*Qi!#%+R,`ZT&b"I,9$rj%+
R,`ZT&b"I+#J!"#m,2c`!"dKZrpa)E[rB5'lrd+Q03Hlrb%2Zrp!Jf5$C5'lrb$m
m!!%r2!!"U+P)E[r)U+0#Tbm,U4FJAbKS!!JJ$'B%B!!!d%KZrrLSLc!Zrp$3E[r
i8N!q!%*R,blrj+PJ1Kp#Tbm,U4FJAceS!"MraN*'B!!!Q#!'d%8J9,"3E34J!!#
5-#lrdP4!,`"#Cbm%U@!b(b!Ijd'3!%%r!$m(U*-J9#!'d%A"r!!')(!!)#m3U)3
J9#!'d%A"r!!'%M!!*'Fb5'lrl$!Zrp*53$m!)!H3!'lrq$m!-#lreP0!2`!`,[r
kd%Fb,[rqdN!r!DLR5'lrl+LN-#lrq0"(-LlrqY*!-#lrrY""2J"54QN)['lraQm
!rf4-haM`6PiZRdje6PErlLmZ!!Sr,J!)5'lrrNKZrrT)E[rbUBdpE[rbrqipE[r
drr#SRMmZrr!r,[rZU*-`,[rZX'lrpQ`D3QFr2!!#U*3`,[rZ9%!p32rZ3QG#CkL
5B0a1AL"IA%p1d%j@rpT)j`%B,bi!#MmZ!!K)E[rq5'lrqNKZrr+TM8KZrr+SSd+
R,bi!#UNA)&mSD!!))!aQ"'!!!**)E[rQU)Y#TkMB*Pm[#kKk5'lrmUKl3QFJ9%K
S!!5SM$iI-#lrq*!!E[rdX%G[1M!ZrrL3!'lrp*!!4dM!JI`!!Y"Zrr3r!$!Zrr*
@3$)ZrrD5E[rb5-'$r!!#dN!r!DL6)&4)D!!%U)4J)#!8@)"5J#m!)&33+!!%!N!
!rdM!,`")E[rb2c`!!DR1,`ZSH5m,U0P-haL!6PiJAea26Y"19[r-51F2'%+RU0J
SAd+RU0JQAd+R,bi!#+NA,KmJ4bS3,bi!#$mm!!G)E[rF5'lrf%KZrp#TM5"(1#J
!&%*R,`@TB$`I5'lriUL,)!53!%Bb,[rNdQlriM3ZrqM83F(#28$rqNKZrr)r,[r
52blrd$mZrpBr,[r8U+G)E[rb2c`!!6mm!!'SU8KZrr*#CcmZrrS[$+M[,`ZSHL"
85'J!!UKl,bi!#%kkr1![#kKj)%Fa4J!8,`ZSf5m-U0P-haM`6PiZRdje6PB!!#m
-+'i!#JaZ!"8!#&I!CMB[!%*R,`bTB$)I)"p)jm!!3QF[$+PL0"p-h`!$Y%&H`F!
"Ca)[$%*R,`bTB$!I8N!r!+PMB$i-EJ!8!!KA`'Bd,`"#Cbm-U@!b(b!I51I!!%*
R,`bTB63I60m!!l4"AF(!!@F3,`a#Cbm-U@!`(e0!2`#TBb"8,bJ!"%kkrUSSAdj
H)&pF6dl36PB!!#m-+'i!#JaZ!"B!#'BJ,`a#Cbm-U@"#Tb"8,bJ!"+NA)&m`(j!
!D!!B2`#TBf!Q$'i!&`!)CKi[$%*R,`bTB%+R)&3[+!!%U4FJAc!S!"M3Acm!U@0
#Cbm-U@"#Cbm-U@)`(l"IE!`[$%*R,`bTBUPMB"a#Cbm-U@"#Cbm-U@%`(l"IE`S
[$%*R,`bTBDPM)&3[+!!%6VVq"#KI6PiJAea26Y"19[rd51F$!%KZrrLTFN*R,bl
rq#mZ!!K)E[rmU@`q(dT(Ch``"`4!!"4R&&0!CbC63'Fi8d"R3!4!!'TR4Q"Q3QF
[,[rm,blrq%(krR)[#+PS2"pJ8%*R,blrr#mZrrK"q[jF,`LTD$`IB$S[,[rm2`G
1Z[lQB#i[,[rm2`G1Z[lDB#*#CbmZrr`[,[ri3UHTD%TIC`J[,J!)6VVpA'!'2c`
!!kR)60m!`%jH,Tp1G8j@rp4)j`mB3UHSf#KI3UHSf#CI3UF[,J!)U4FZ(b"(+LJ
!"#mZ!!Jr2!!(5'lrj%KZrq")E[rBUBdJ4cJS!"C#Cbm&U@!m(b!%N!"'jd!p32r
k5'lrmMmZrpSr,[rB2blrhMmZrpbSTdKZrr)r2!!"2c`!!DLT5'lrmMmZrrT#Cbm
-U1m[#kKk)&4)D!!#U(X[,J!)6VVk1Lm,U(NJ4c&'!"B[#kMC,`bSf8cI'2"1ALk
I6R919J!!,``SEJ!+$'i!&3!)9m"Q0Lm!3QF[$+PJ-KmJ(dMR`!"#Cbm-U@)d(dc
I!!1d39l"`!&R%Lm-3QF[$+PJ-"p53$m!U@0J2JaZ!"3!#&I!CM3[!%*R,`bTB$)
I)"p)jm!!3QF[$+PK0"p-h`!$Y%&G`F!"Ca![$%*R,`bTB$!I8d!r!+PM)&3[+!!
%6VVq[#KI6PiJAea26Y"19J!!,``SEJ!+$'i!&J!)CK)[$%*R,`bTB$!I@d!r!+P
MB"J-EJ!A!!KQ%#m-3QF[$+PJ-"pD3$m!U@0#Cbm-U@"#Cbm-U@)`(l"IE!`[$%*
R,`bTBUPMB"a#Cbm-U@"#Cbm-U@%`(l"IE`S[$%*R,`bTBDPM)&3[+!!%6VVq-LK
I6PiJAea26Y"19[rd51F$!%KZrrLTFN*R,blrq#mZ!!K)E[rmU@`q(dT(CeS`"`4
!!"4R&&0!Ca"63'FL8d"R(J4!!'TR*'"%3QF[,[rm,blrq%(krSi[#+PS2"pJ,Lm
Zrr`r"dkkraKJ)N*R,blrr#mZrrK#TkPS5PpR##mZ!!K1Z[fXB!Br2!!$UFK-h`$
!6PiZRdje6PErkLm-,bi!#MmZ!!K)E[rq5'lrqNKZrr+TM8+RU0JSAbm-U(T)E[r
bU(Y)E[rbU+%[,J!+6VVi-#m-U(N[$+MC+&p1AL"IA%p1d%j@rqK)j`-)3UF[,J!
)U4FJAbKS!!K#"L"8-""63$e!rqK#4f!8)&3J"m(m!!B5-!!NC`*m!9*(D3DqE[r
SEqB[,J!)2c`!!8KZrrC)E[rb5'lrkUQ0%!CR#LmZrr*#CkPGB!S[,[rb2c`!rkP
G60m3`%jH,Tp1G8j@rmT)j`m)3UF[,J!1U4FJAbKS!!JJ$'B+2c`!!kR)B!!$1NK
Zrr+SLc!Zrr63E[rb-Llrq0*!1!%[,J!12c`!"dKZrq*)E[rH5'lreUQ0HJ*#Cbm
Z!!UTBMeIrmj#CbmZ!!UTB$iI3QHTFa!IC`!#j+Qd5'lrr+Pb-#lreP*!X'lrr'm
!!*4+4fm1,bi!#L!(8d!r!+PMB!*Jc&0(5)8`"@Fm8d"R5&0!C`*J6L"8)!I"r!!
'%M!!*'F%HJ&J!N)&)&3J"m(m!!B5-!!N#J%!!5"8)!I"r!!'%B%!*'!F)&3J"m(
m!!B4[!!"!#4J$#"8)!I"r!!'3M!!*$e(rmS[,J!16VVj&R!%,`")E[r56VSBbQ!
!re*J%M!ZrpC53$)Zrrb53%M"Jm3m!6!Z!!K63,"'E!!![,jZrmjX$LmZ!!SJ"e*
!2`#TBf!%B!$r'P*(5)8`"@G18d"RB&0!C`*JE#"8-#i!#&0!d%I"r!!'%M!!*'F
%HJ&J!N)&)&3`,J!)8d$34m(m!!B5-!!N#J%!!5"8-#i!#&0!d%I"r!!'%B%!*'!
S)&3`,J!)8d$34m(m!!B4[!!"!#4J%L"8-#i!#&0!d%I"r!!'3M!!*$!Z!!K630"
(28$rbLmZ!!j1Z[JmF!3[!%KZrp*1ZKI`B!$qH#!'d%FJ9,"3E34J!2jU5)8`"@F
!!)C63'F!!1*63'F%B!!"0#"8)!E34m(m!!B5-!!NC`4k!@!#3J8J9#!'d%I"r!!
'%M!!*!S"!!%J9#!'d%I"r!!'%B%!*$!ZrpK53$e!rq``,[r@8N!L"X2%dN!p3Ir
U-#lrh&0!28$rm$!ZrpC53#)'8N($a0*!28(rlNKZrqUST'!!!,iJ9#!'d%I"r!!
'%M!!*!S"!!&R6#"8)!E34m(m!!B4[!!"!#3`,[rB8N!p32rX-#lreP*!)JE$a0*
!28(rkM!Zrpa63$e!rr!`,[r@8N!L"P*"`m653$e"rqj)E[rUU+4J@L"8)!E34m(
m!!B5-!!NCdSJ9#!'d%I"r!!'3M!!*$!ZrpK53$e!rq``,[r@8N!L"X2%dN!p3Ir
U-#lrh&0!28$rm$!ZrpC53#)'8N($a0*!28(rlNKZrqUST#!'d%Fp32r+B!$p&Lm
Z!!j1Z[`Z60m3m%jH)&rHr!!+6Y"19[rX51F$##"Z!!K$l[ri5K!Lf#,B+'i!%M`
Z!"!J9$!38d!p32rX3NGJG#"8)!I"r!!'%M!!*'GL)&3J"m(m!!C#-!!N[%GI`'j
3)JH54V*Z!!jG`F!"Cd*)E[rZ-#lrqP*!2`!`,[ri8N!L"j*'`qi!$0*!2`%`,[r
q8d!r!$!ZrrK53#)(NNC53F2Z!!c53$m"U+G)E[rZU+454fN'[Qlrl'q'60m3`%j
H)&rHr!!16Y"19[r+51F2'#CZ!!Si,J!)3UF[,J!1U4FJAbKS!!JJ$'B+2c`!!kR
)B!!#5%KZrr+SLc!Zrr63E[rb-Llrq0*!2!%[,J!12c`!"dKZrq*)E[rH5'lreUQ
03QF[#kPL29rrd%*R,`ZTB$iI,``r"cm%2`C)E[r@6VVqe%*RUA-3(fF!!CDTY%K
ZrrbTFM!ZrpC53,"Zrra[D%*R,`ZTB$iI5NG[-#"8)!G63-(m!!B5-!!N#J%!!@F
3,``r"cm%2`C)E[r@6VVqKLm,)!G63$m!U@0J!Q#N)&3J"e0!`I`!"K'm!!%!*#m
Z!!j1Z[85F!3[!%KZrp*1ZK6'B!$rIQ!5-#lreP*!-Llrr**!5-'$aMS")!463,"
&E(C#Cbm,U@!q(ljZrp"X0L"8)!G53#)%8d(53-2m!!B3-"!N#J!!!@F3,``r"cm
%2`C)E[r@6VVpr#m,)!G53$m!U@0J"'!!raSJ9#!(8N!L"&0"dN$$r!!'%E`!!4!
N,bi!$Nkkp)"`"#m!5'lrdNkk&$4J!2lX3QF[#kPJ2KmJ"G"()&5`8'd%B!$qeL"
8)!A34m(m!!B5-!!N#J%!!@GD,``r"cm%2`C)E[r@6VVpK#"8)!A34m(m!!B4[!!
"!#4)E[rU-#lrf&*!2`!`,[r@8N!L"F2'dN!r!6!Zrpa63$m!-#lreP*!)J953F2
'dN!r!DLR5'lrkULNB!$qC#mZ!!ir2!!"5'lriNKZrpj)E[r@UBdJ9$!38d!p32r
+3Qlrc'!J)&3`,[r-`I`!"K)`!#4R#LmZrpj#CkPGB"T5E[r-D3S`,[r-X'lrbQr
@,blrhMmm!2qTA8cI'2"1AL"Ih[`!#Nl36PEqJNMR$aJJEJ!-3qlrm%S3)YJLf#,
B)YJq,J!55NGQ$L"m!!!$8LK32L`!6Q"L5NG[-N+R6VS6(L"I+'J!!L!-9X"R$Vj
X!%K@`F!"C`3S9'$X)!aQ#$mmrmJ`(kR*2L`!6Q!X3UG1ZK,X)&mSD!!#)!a@`'F
1[Q`!6PE"`!&R"#K8B1`J$'B)2ccrh6!IUFNpE!!8rST#Th!'`HlqLY"m!#C)`#m
!6VS5BLCI3QFqZ!)J5Pp@`'B))JYA`B!"C`Jr2!!C-"qTb5"6-8F!!L"6@)K$l!!
XF!FJf90!E[T#E[k-3N3pE[k+rS3pI!!"rqaJ!!%N3LlrXMe(rkj#,[kB3HlqQ#e
)rkSpE[rXrl4#Cd(ZrjJ[#%*R6VS54$`I5NCR"Mm'-"qTb3aZrrm!%'G!3J8`,J!
38d!p32k#3QlrlQ!L-#lrlZ9!3IB!m%2ZrlK`!l%*9XMrr'B%HJ&J&&*ZrqjT#M!
Zrqk`E[k#Ep4J!RS"%!9R)%UZ!!K@`F!"CaC#Cd(ZrjJ[##mZ!!K1ZZpZ'Km+"3!
"%!9RFN+R%#lqQ!*!!2p53%M!,`"1ZK&H,9rqP%*R2VJ#)%TI9X"Q#NUZrT4A`B!
"C`Jr2!!C-"qTb8(ZrTJ[##"ZrT3[%"!ZrTJ#3!$r8N")`#m!6VS43#"6)!6"r!!
')DlqP!!J)&-J"-(m!!C#-!!N8N4J"&*ZrSa5E[rXD3``,[rXX'lqK'm!rY3`,[k
+N!"ZrS`J8c#!,`XJ8h!'`G$3I!!Q5-![!%kk%1"#Ccki!L!m(dT'C`Br"M!IUFN
Y5`!860mBm%jH)&rHr!!-6Y"19[rk51F$##KZ!!JJ$'B#B'!J9$!38d!p32rk3NC
J0#"8)!E"r!!'5V!!)'FL)&3J"X(m!!B[-!!J6VS3G%*R2VJ#)$iI5NGR"Mm(-"q
Tb9*'D3DmE[rkEmB[$%kk%&*#Ccki!L!q(dT(C`Br"c!IUFP-ha$!6PiZRdje6PE
rpNMR$`JSEJ!))!aQ"'!!!3a#4#"8-""63$e!rrC#4f!!!0SJ9#!(`I`!"K)`!#3
+!3!"Cc)J9#!(`I`!"Lm`!#"1ZJrZ3QFqZ!)J1Kp+4@F'2`8`(kR*)&3J"m(m!!C
#X!!JB!!!NP*%5NGH`'m!!)JJ9#)(8d($r!!'5V!3)&I"`!&RFL!(8d!m!'!5)&3
J"X(m!!C+X!!JC`*J"P0'5NCXkNT'9m"Q$L"85UJ!)&I"`!&R!Rcr)&3J"m(m!!B
L9#)'8N($r!!')l!!)"!J)&3J"P*!`I`!"K'm!!%!*#"8)!I"r!!'3V!!)#"8)!I
"r!!'3M!!*&*(D3LqE[rfE`$r)L"8-)3[$#"8F!E"d0"m!#C)`#m!6VS2)NcI%2"
1ALkI6R919[rf51F('#KZ!!Km!4!'C`!!kN)')&3`%&9!28$rpN*(B!!!c%*R)&3
J"m(m!!BJF!!J)""5J#m!)&3J"e*!`I`!"L"`!#!J%&+!,`!J9#!(`I`!"L"`!#!
J8"!3!N!!rcm!)&3J"e*!`I`!"L"`!#!J8"!3!N!!rcm!6VS2!JaI!!&QCR`")&3
J"m(m!!BQF!!J)&3J"m(m!!BD-!!N)&3J"e*!`I`!"L*8)JI$r!!')l!!)"!J)&3
J"e*!`I`!"L*8)JI$r!!'%l!!*"!N)&3J"e*!`I`!"L',!#!J9#!(8N$"r!!'%B8
!*&*(D3LqE[rfE`$r-'!!ra4-haMJ6PiZRdje6PErr#!Z!!KAJ#e!rr`JE[rm%""
)J'XBX(`!$fi53IS!*M)!jNP%33%`%2m+2!!%CJJGI!!"!!aJ"%)Z!!a1ALkI6R8
""Nj@rpK)j`mB3UF[,J!)U4FQAbK6,#X!"#iV!!JpD`!Brq4+KfB),`a#CkPPB#3
J4b"3-"#`E[rNE4)[$#"()&!`%*!!E[rN2`#TC@!',`a#CkPP,`a#CkPM3QF[$+P
L$&m!!@`+,``r2!$rU9eJ"Lm-3QHTA5m'3QHTBdU(CJ`["Mmm!2qTA@!!!)a#4#"
()&!`%&0!28$rf%*ZrpaJ*%*R)%FJ8$!Zrpc"r!!')(!!)#m3U)`k(lK&E!)i"9*
ZrpaT#M!Zrpb`E[rBEp)[,J!)2c`!"dKZrrj)E[rk5'lrmUQ0-#lrq*!!E[rd@%!
k!,T%E3S["Mmm!2qTA@!B,`BJ"*!!48M!JI`!#&4!2`#TC5m'3QHTA5mZ!!Jr2!!
"5'lrrNKZrrT)E[rbUBd[,[rk2c`!rkPG3QX!&%*V!"C-haM`6PiZRdje6PErcNM
R$aJQEJ!3+Li!$%*R2c`!J%KZrr#TF"!IC`!"%N+R,`ZT&bKI3QF[,[rbU'T+AfF
Q2A`!H2rX2A`!P[rZ3QF[,[rX,blrmNkk$*T+AfB%I!&J"N)'B!*m!4!'C`!!d#m
X!!K1Z[YJ3UG#CbmZrr+SDcmX!"T)E!!F,b`!%%kkq0iTA`!),b`!#%kkr0i[#dk
krL4#4%+R6VS-%#"I,LJ!!NU(Ca4#Cbm(6VVpa"!IC`*54#"(,K"Jk!a%!!&[(#m
,2c`!"NKZrqK)E[rN5'lrh+Q0,blrj%*RU9d[#cmm!!9)E[rS5'lrj%KZrpbTM5m
Zrq4#CkPG3UG1ZJZZ)&mTD!!#!!`JE!!-,`K#CbmZrr+SDc!I)&q`D!!'C`SJE!!
-+9!!$'$H5'X!%+NS)%8-8!!"CQB[#cmm!!G)E[rS5'lrj%KZrpbTM5"&,@J!#[r
B5'lrf+Ka3QF[,[rB5'lrh+LY%"pR0%+R)%8`+!!15-![!#mm!!!#!+KB5TpR#L"
Z!!J`[!$rB!JJEJ!)-,`!"aem!!%!&'!!!2JJ46!3DaL`I!!(EK*"qJ%B-J$Q584
"!6!3r`Sm!!4A`'B!!0![!%+R)%8b+!!15-%[!5m!3UF[2!!!#!![2!!!!3#S@b)
I)"m[!DKB)KmJ(dU"9m(!!@F!!*S[!%+R)%8[+!!#,c`!N!2rU&JL(b!I$%%!$9I
"`!&RH#m,2c`!!8KZrqK)E[rN5'lrh+Q0-#lriY"Zrpj)`)(m!!)p32rD-#lri0"
Zrpa)`)(m!!)p32rB3QF[,[rN,blrf+PQ-"pV',"m!2pZ%N(k!&Sb!1C*4%%"-"$
r#M`!"'B'3Li!&'!8)'i!#$#m!!%GI!!"!"4J"%)Z!"4-haM`6PiJAplm!!a1d%!
!N"i"!#K19[lm51F2'#"Z!!j$l[r`5K!Lf#,B)YJLf$JZ!")YEJ!8riJp42q5$%6
rrfmN)!463$e!r[a#4f!5)!IP3#)(j8%YYK$`!*454fN'[Qlqr'rS3Ulr#N+R2cc
`Ad+RF2m[!+Pm+&m[$$mZ!"Sr,J!B3QHT'dKZra5SG#m-U(-[$%(ZrhJ[#+NB,``
r2!!)5'lrk%KZrq4)E[rFUBe#Tbm-5'lrh%Kk#1JI2!!"3QG#Cd*R2c`!%%+RU93
YArpi,``r2!!,5'lrk%KZrq4)E[rFUBd[$$mm!!G)E[rS5'lrj%KZrp5TM8+R,`a
)E[rF5(S)RKmm!!&#Cd*R-#lrfNM!JI`!#$m!2c`!%%+RU93YArpm,``r2!!-5'l
rk%KZrq4)E[rFUBe#Tc!Z!!K)`#m!F!%[!+KB5TpQ##mZrq5T@'!H3UF`,J!)5-!
[!(!#,`#S@%UICJS[,[rN2c`!rkPG3UF`,J!)5-![!(!%,`#S@%UICJ4#"@!#HJ%
[,[rN5)8r"DPM3QHSKcmk#!+SL$mm!!'SL6mm!!bSLNKZrfUSLbm-2c`!"dKZrqK
)E[rN5'lrh+Q0-#lri*!!E[rF9d!b,[pXdQlrDM3Zrh$838M!JF*53$e!rj!!3UG
1ZJJ1)&mYD!!#ri3JE[q%5T!!CQ`[$$mm!!C)E[rS5'lrj%KZrpbTM5mZrq5T@%*
R,blrK%kkqD!3(fFH3UFJE[q%2bJ!"Mm%5'lrm#mZ!"41Z[4b,9rrJ'!L3UlrJ#m
-2c`!"8KZrqK)E[rN5'lrh+Q0,blrj$mm!2qTA@!!!HK#4d+R6VS(LL"I,#J!!NU
'Ca4#Cbm'6VVj2K!IC`*54b"',""Jk%T(CN4#V[q!,``r2!!&5'lrk%KZrq4)E[r
FUBd[,[rN2c`!rkPG,``r2!!'5'lrk%KZrq4)E[rFUBd[,[rN2c`!rkPGB!!"HJa
(!!&QD%+R6VS('#"I,@J!![q%3QF[,[q%6VVic"!I#J!!!@F+)'lrK#e3ri4Jj%+
R)'lrK$mS!!Br"%KZrr![,J!86VVcN!!YArq!,``r2!!'5'lrk%KZrq4)E[rFUBd
[,[rN2c`!rkPGB!!"$%+R6VS'ZL"I*QJ!!L"m!!!#&$i3)!G%3$i!)!Y@`'F1[QX
!6PE"`!&R"#C6B1`J#fFd3UG1ZJCq)&mYD!!#ri4+V[q%9X"R(#"Zri3b+!!'XQX
!5&E"`!&R#L"Zri3Y82q%B0aJ1L"m!!!$8LC33UG1ZJC#)&mYD!!#ri4+V[q%9X"
R(#"Zri3b+!!'XQX!5&E"`!&R#L"Zri3Y82q%B0a+V[q%9m"R(#m!3QF[,[q%6VV
hb")I)"m+!3!"J!%#3!!"CbT#Tdkk"H`JAbeS!!,rK%*R,blrK%kkpk!3(`S!!!&
R#L"Zri3Y82q%B14#Tb"Zri3r+!!'2`4)E[r`,bi!&%kkmQ3YArq!5UlrJ'F),bl
rJ%kkpPi[$$mm!!P)E[rS5'lrj%KZrpbTM8(kj)3Y52rN,``r2!!*2blrk#mZrq4
)E[rFUBi[$$mm!!G)E[rS5'lrj%KZrpbTM8(kkUJY52rN,``r2!!(2blrk#mZrq4
)E[rFUBi[$$mm!!4)E[rS5'lrj%KZrpbTM8(kj))Y52rN,``r2!!%2blrk#mZrq4
)E[rFUBi[$%kkpa3[$+N9,``r2!!)5'lrk%KZrq4)E[rFUBd`,[rJ8d!p32r82@l
ri[rD,``r2!!,5'lrk%KZrq4)E[rFUBd`,[rL8d!p32r@2@lri2rB5'lre+LK5'l
re$mm!!)r2!!#U+N`,[rDN!"ZrpB-3!!#E4C)E[r8U+&)E[r82c`!!Mmm!!+SU@$
F3IVhdLm)5'lrkUQ4-#lrkPY!CbT63'F!!BC63'F!!QT63'F!!`KA3'F!!`T63'F
!!``%3!$cC`!#"'!!!c4#V[mX)'lrJ#"32@J!![m`3QG"l[mD,`K1ZJ4329rr'%T
ZraKQ!!%f,blrJ%kkmc)JE[q%,9$rK%UZri4@`'FL,`"#CbmZri41Z[A!%KmJ(`S
"!!(!!@F+)'lrK#e3ri4JeNUZri4Q1%+R6VS$f#"I,@J!![q%5UlrK&E!Cb)[!%*
R,blrK%kkpB)5(b!I#J%!!F!"C`SJE[q%,9$rK'$@5UlrK'C#3UlrJ#m-2c`!"8K
ZrqK)E[rN5'lrh+Q0,blrj$mm!2qTA5m-2c`!"NKZrqK)E[rN5'lrh+Q0,blrj$m
m!2qTA@"S3UFJE[q%2bJ!"Mm%5'lrm#mZ!"41ZZrk,9rrJ#mZri"1Z[2kIJ%JE[q
%,""+KQF83QF["Nkkp1`3(fF#8NFJ4L`3B1J-4`!#E"i[$$mm!!C)E[rS5'lrj%K
ZrpbTM5mZrq3r2!$rU9d[$%kkp2a)E!!3U5KJ!!(8,blrJ%kkmISJE[q%,9$rK%U
Zri4@`'FL,`"#CbmZri41Z[5)%KmJ(`S"!!(!!@F+)'lrK#e3ri4JeNUZri4Q+N+
R6VS#S#"I,@J!![q%3QF[,[q%6VVd9"!I#J!!!@F+)'lrK#e3ri4Jj%+R)'lrK$m
S!!Br"%KZrr![,J!86VV['#eIri![,[q!6VVc'#m-6VVdANKX!"#T+'!!!6B3"@F
3,``[,[pi2blrN!"1ZZK-B$3[$$mm!!a)E[rS5'lrj%KZrpbTM5mZrq3r2!!"U@-
[$#mZrhJr,[q3!%kkk"i[,[rN3QHTBf!!!1S3"@F5,``[,[pi2blrN!"1ZZJ!B!!
!e%+RUA8J(j!!V[m+,`"#Tbki![!L(b!IXS"[9NKZr`+TFM!Zr`53!'lr#'S#4%!
-3!!$AF"X(M)Zr`+5E[m'DJ*%33a"!!0G`F!"C`JpI!!"rqTJ1LeZr`,r"Lm-,bl
rH$mZrj!!6VVVY%+RUA8YArm+B'*)E[m'UA)[$#mZrhJr,[q3!%kkkjC#TkPe,9r
r#Q"%,`a1ZZ1-B$`[$%kkjJjJ0#m-2c`!$%KZrqK)E[rN5'lrh+Q0%!9R#LmZrq4
#CkPMB!S[,[rN2c`!!DPM)!8+!!!"'J!`,[rUDaL`I!!(EK*"qJ#S-J$Q584"!6!
3r`Sm!!4Q!2a`5UlrK'G!3UG1ZJ$`)&mQD!!#)!Y@`'F@)'lrK$)V!%LbD!!'9X(
!!@F%*P0Jj#!,Ca3YI!!!!K6qrM!V!%j%3#"Zr[i`J%UZri"R(#mZri"1Z[!f$'i
!!IrUC``[,[q!6VV[UN+Zri![,[piU98[,[pmU98[$+Q$,blr&+Kc,@lrJ!!F60m
Bm%jH)&rHr!!86Y!!#J#3"#*I)"qJ6#k!6R&`!#m*-F!#)%jeF!"JpL*I)"qK)Lk
)6[VrkL*I)&qJ)dlkrq!LAb!I)&qJ*%lkrp3L(b!I)PmJAk!Z)N&1q[r%)"mLAb"
I,`#J1b+!6R8[I!!!!`J!"%je,h`!!!0@!!41G5*I%"mJAfB%S!aJ!U3-2S"1d5*
I)&qJ&ck!6Y&d!L"I2`)[#+hTG!"1q[rd0$`!#Nlk!!Bd2!!-)&mr!Lm)VHd!!!1
B!!!$U!!!#m)!!!1)!*!$)!4N2c`!!DR`(CJr2!!"UI!&)$mm!!'Tm#j82c`!!DR
`,P3r2!!"UI!Z9$mm!!'Tm",m2c`!!DR`%c3r2!!"UI!@EMmm!!'Tm#jL2c`!!DR
`,&Sr2!!"UI!+MMmm!!'Tm!ZN2c`!!DR`"D3r2!!"UI!&"$mm!!'Tm!@82c`!!DR
`"8`r2!!"UI!1*Mmm!!'Tm!fi2c`!!DR`&2`r2!!"UI!(IMmm!!'Tm"2X2c`!!DR
`&+!r2!!"UI!'Z$mm!!'Tm!Cq2c`!!DR`"Y3r2!!"UI!84$mm!!'Tm#%B2c`!!DR
`"b!r2!!"UI!+lMmm!!'Tm#Ab2c`!!DR`)83r2!!"UI!M6Mmm!!'Tm#SU2c`!!DR
`*S)r2!!"UI!QB$mm!!'Tm#G12c`!!DR`*N`r2!!"UI!RCMmm!!'Tm#Gf2c`!!DR
`+!3r2!!"UI!IX$mm!!'Tm#"i2c`!!DR`"-3r2!!"UI!0D$mm!!'Tm!8D2c`!!DR
`#R)r2!!"UI!0)Mmm!!'Tm!812c`!!DR`"2Jr2!!"UI!6IMmm!!'Tm!J82c`!!DR
`"FSr2!!"UI!'%Mmm!!'Tm!BN2c`!!DR`"J!r2!!"UI!'@Mmm!!'Tm!Bf2c`!!DR
`"EJr2!!"UI!&h$mm!!'Tm!C)2c`!!DR`"Q`r2!!"UI!&lMmm!!'Tm#dL2c`!!DR
`",Sr2!!"UI!(S$mm!!'Tm!IL2c`!!DR`"e`r2!!"UI!,a$mm!!'Tm!ZX2c`!!DR
`#!Sr2!!"UI!CN!!r2!!"UI!ZA$mm!!'Tm"H82c`!!DR`'!ir2!!"UI!@d$mm!!'
Tm"DS2c`!!DR`&V`r2!!"UI!DRMmm!!'Tm"TU2c`!!DR`'[Jr2!!"UI!D3$mm!!'
Tm"R!2c`!!DR`#8`r2!!"UI!*"Mmm!!'Tm!S'2c`!!DR`#%ir2!!"UI!)XMmm!!'
Tm"8k2c`!!DR`&9ir2!!"UI!$&$mm!!+Tm!Mk2c`!!UR`!d)r2!!#UI!$Z$mm!!+
Tm",-2c`!!UR`!!!r2!!$UI!$HMmm!!1Tm!&'2c`!!kR`!!Jr2!!$UI!#9Mmm!!1
Tm!!!2c`!"+R`!*3r2!!&UI!!,$mm!!DTm!%Z2c`!"UR`!@Sr2!!'UI!!'$mm!!D
Tm!"H2c`!"UR`!!!r2!!'UI!!F$mm!!DTm!'f2c`!"UR`"%Jr2!!'UI!$0$mm!!D
Tm!(d2c`!"UR`!!!ZLJ#3!eT19[rQ,`a#CbmZ!!LSDMYIrp"#CbmZ!!LSDcYIrp)
`,Ir38d"R&J4!!2pR,P0!C`!"%&0!C`!"fQ!!!X3[,Ir`2bhrdNKYrX#T4N*R5'h
q`+Qf1errfQ!!!UB`,Ir58d"R'&0!Ca463'GL8d"R!!$#8d"R!!$#B!!#KLmYrr3
r2!!$U6T+VIUSCL*#TbmYqN4"l3,k,`Jr2"J!5(S#YN+R6VS@,LYIqUK1ZKB5$'d
!!Ir5CJJlI!!+qUCJ"MYm!![kTLmYqUK1ZKAJB("#CdkY!`S3(fGB'h`!!Ikr3UF
[,IT%3Hd#mLm)2c`B!%Kk!Pa#Tdkk&G`VArUX,bhrp$mm!!-I2!!"U88[,Iri3QH
T1LmYrr3r2!!"U6S[,Ird2c`!!UNk,bhrp$mm!!1T1Q!-6Ud$%Q!''h`!!IrYB!!
"`$!Yrp*63'X!!-!-3!!&EJ!!Z10)-$X!"Nll!*!$$J!B!#)!,!!d!&3lI!!"qV*
J!!#B1h`!![UbB!!!MMYm!!6kXQ!!!)3lI!!&qV*JHN*R5'hq`%kk'LB3(fF33Hh
m[%2YrX"`3#$C8d"ZqQ"D3UFr2!!m3UG`rbm!UA`SAbm-2c`!!dKZrqj)E[rk5'l
rjUQ0,blrqNKYrEbTMbm-2c`!!d*R2c`qJ+Pq3UG)E[rdUC%-EJ!"rr4Q#LmZrrT
)EIfmUC!!,`bTJdkY!`*J!!$`-#hrdP0!Ca*63'F@8d"R1P0!C`!!eQ!!!0C1V31
5B!!!cN+R3UG1ZL8Z5'he0%kk+KT#TdKk!1j1ZL`53Hhe0#m)6VS,&'!!!+C#CdK
ZrrC)E[ri6VS)L%TICJ!!M%(Yp64$qJ#f)0NJf6#4-#lrq%M!,`")EIl!6VS&LNK
YrX")EI8d%#he0!*!!2p53$m!6VS'%%Kk!(T)EI8d%#he0!*!!2p53$m!6VS&q$!
ZrrC)`#m!5'hq`%kk"8T)EIl!5'he0"!Yp63#3!$r8N!r!%kk"G"#TdKk!#*1ZLY
`3Hhe0#m)6VS+FQ!%6VSSLN*RU6JSAdjH,Tp1G490H5""F("XCA4KE'XJB@4NFQ9
cFcS),#"1Ef4P1L!!#8jPG(G[FQXk)"40H5"*ER4PFQjPG#"KC'4bCA0c1J!'8f9
bGQ9b!!C%Ee9cCA)!6PErqUQd3QFr22rr5'hrh+P`%"pR!!$H3QG)EIrFUAm3(fG
13QG)EIrF5'lrr%KZrrUTJ"!I#J!!!@Ff$'d!!IrFCK*#CdKYrpa1ZJM3%"m+!!!
"B"J-E3!)rpaQ%%*R5'hrh%kk#3B3(`S!!!&J!!#1-#hrh&0!C`T93'GZ98"RDQ"
k3QF[,IrQ5'hre+NX1errf$!YrpK63'F58d"R(&0!Cc463'FJ8d"R,'"33UF[,Ir
QU6e1Z[[ZB%*)EIrF,bhre+QcB$B[,Ir8,bhrjNKYrm#T*@!Q3UHT*#!Yrp5`RfF
',bhre+NIB")r2!!#UFKJ#NTYrpaQ"%kk%QB3,IrYC`$r!%jH6R91ZJ2!6PB!!#a
I6P8!!*rY!""1ZJ2#6Ud$5N(Y!dS[#+Ra6VVqdNkk*U"1ZKF!6VS$XNjG6VS$RNj
e6Pj1G5*I)"qJ6#k!6R&`!#m*-F!#)%jeF!"JpL*I)&qJ,8lkrqa+1!+1DLSJH!%
`3rJ"&#!)N!#4G!b`JQ8@)P%Kb!%8)S"#%5*i!USLL###dDN!$%jeS'01G5*I)"q
K(Lk)6[VrV#*I)&qJ(dlkrk)LAb!IS5)ZL%lkrjDJ0Nlkrj)L(b!I)PmJAk!Z)N&
1q[q!)PmJAbm*S$-r3!!%6R8LAb"I,`QJ0$p!!!41G8j@!!!JEJ!3)Qi!$(!!%"K
)3"!C5Li!#'F15Li!#QF%T$aJ%+!mB!a+,J!+C`5Q2'!#SM`+!!!"(8!!&%jH)&r
Ir!#3!`a1d#*[!!3JE`!)U@mJAe"26Y!LE`!%)'m!#+PZ2d!!$#"I8%p1d#*I%"m
JAfB%S!"J!U3!2S"1d5*I%"mJAfB%S!&J!U3"2S"1d5*I%"mJAfB%S!*J!U3#2S"
1d5*I%"mJAfB%S!0J!U3$2S"1d5*I%"mJAfB%S"0J!U362S"1d5*I%"mJAfB%S!K
J!U3)2S"1d5*I%"mJAfB%S!PJ!U3*2S"1d5*I%"mJAfB%S!TJ!U3+2S"1d5*I%"m
JAfB%S!eJ!U302S"1d5*I%"mJAfB%S"&J!U342S"1d5*I%"mJAfB%S%4J!U4%2S"
1d8j@rmj"l[r1)@i!$J!5-@i!$!!@3LJ!'N)S!"Y#U!!FS!!LEJ!)-UJ!'$e!!"*
1AL"Ihr`!N!-+6Y"19[r13HlrcM&Z!!J!'+!"28!!#NjH)&p8Mdl38F&J!P$"6PE
rcN(ZrmiKEJ!)!#!aEJ!3!"JLEJ!-)9%!*%*S!#a#U!!Z5J&Q"+!#B!+J!ce!!")
LEJ!-)UJ!+%jH)PrIr!#3!`T1d8j@rl""l[q`)@i!$J!5-@i!$!!@3LJ!'N*S!"b
J$$e!!"*"k!!J)Qi!#$!m!"#J,NjH)PrIr!#3!`T1d8j@rm""l[r!)@i!#J!5-@i
!#!!@S"8p3!!16PiLAeb26Y&19[r!3Hlr`$&Z!!J!&L&Z!!S!%U!628!!$NjH)Pp
FMdl46PErX%(Zrl!KEJ!1!")aEJ!-!"C#+!!D3QJ!(+!-3qJ!)#"Z!!J`2!!3S#j
"l[q`S!dp3!!56PiLAprm!*!$#Nl46PErcN(ZrmiaEJ!1!"JaEJ!-!#`KEJ!)!#k
J4$e!!""1AL*I8)p1dA3")&mr!Lm)VHSJE`!%)#m!#%*RUHiJAe"26Y"#V`!33IS
!###[!!41G3#3"%je)&p1A5m)6VS"qNje)&p193!!6Y"19J!!,&p)jq$J-#m!(#"
[!"j$l`!L0!$P5Y,#3N&5L'!3*'&#3K3DdN*J!K$D8FVrr&()rqiJE`!H%)&$l`!
L-#m!(19)dX!M,`!B,dN!'%cI"`FZAdje6Pj1G59I3d&8)*!$6PB!!#aI)Km`(b*
I)&m[!8MR%#"63'dU3N%5'%*#&"'d3'dH0J(@3K,$*%R8`p,#P%"J!K8K8FVrr'!
#%YK4bIrm60m%#%je6Pj1G59I58j6)*!$6PB!!#aI,`![!6![!"$"l`!1-Lm!$-2
[!",338K!3N!b,`!5`Zm!$Y#",d!!%#)I)"m[9`!%@)p1G8jH6R8P59p098`d)%j
@!!![!#m")#m!&#)[!""1ZJ!8,d%!&#)I)"p1ALpA!!4BMdje6PB!!#aI51Fq!#S
!DJ*%J#`"DJ*%J53"5%*+3QBF0J"#3%K!C`5!`63!5%)`!i$"0!!L!N*!5%"J(#3
!*J&#J%+"H"r8JY'!dS'`Jfd%N!#$8J&4c2r`5S9U!N5!ZiCU!N5"60m!I%je6Pj
1G8P%59C06d3J6PB!!#aI)(Vq4Nl36Pj1G59I5%&-9#!J51I!`()"B!C)jm$!3N%
JE`!B)Qm!&%*!%"L`'@B1B!5c#'B)8d"Uq!T"!!%I33!D,fm!%!!@60m$!eb26R9
19J!!3UhkM%)YqSC#,IU(3UhkJ%+YqRa#VIT`6Pj1GD9*6NP85%9"!!"`!'!!!)T
`!@!!!)4`!Q"qF!0JHR!%B(C`"@"bF!CJER!(B'T`#'"QF!PJBR!+B&j`#f"DF!a
J9R!0B&*`$Q"1F!pJ5R!3B%C`%@"#F"*J2R!6B$T`&'!fF"9J-R!@B#j`&f!UF"K
J*R!CB#*`'Q!HF"YJ'R!FB"C`(@!5F"jJ$R!IB!T`)'!'F#&J!R!L(`!`H!$JX2J
#VQ3!!!iJH!$J)&!J8%U3!'BN98m[2'&dF'bTR$!ICb*C6bmmBA4`E$m!UCdJ(fF
-)%!I2!!M,a"#&dje-$J+B'B%-$cr3(3!FJ!8(b*I3IS!'K)`)!$H`3a#!#*R#Ja
#!!&Z!N*!2S"1d3#3"!B#"JB%#!))#!3!!!J#"T!&"!D3"!i'"!!!#!#3"JT64Ne
(CA4'D@aP!&024P41ZJ!f6Y&1ZJ!`6ZN!"%kk!#K1k3!)6VS!)%lT!!a1ZJ!B6ZN
!%%(krm3J8+!U3IVr[#"3S%P1G8(krl*+N!"R##"35T!!CLbJ)d+R,cVrX%Kkrk#
TS5!ICJB`2!"MUFP"q[q-))![!+Q53IVrJL"3)P"1G3L3!!!'#0!!"b*36R919[r
k51F"##KYp5`J$&E!Ca)L,!!'XUi!#&E"`!&R"#K8B1JJ$'FN3QF[$%(Yp5S[#%k
kq93q(bmZ!!LTJbm-6VViTKem!!%!$'!%3Li!$%cI%)"1ALkI6R919[rU)'i!#%2
Zrr!Lf#,B)YJLf%*R,blrqNKZrqbT,$eIrqSJ,[rXX+hf0'B1,bhf0+N@(A`!!3!
-B!j#CbmZrqa1Z[pL(9m!$%jH,Tp1G8j@rp`[$#"Z!!K$l[r`)YJLf#,B)YJSE[r
bZHhf0'B)(A`!!3!-B#a#Tc!Zrrj)`#m!F!%[!+KB$*m!N!-"CJJGI!!"!!aJ$%*
R,`a1Z[m)(9m!$#KI6PiZRdje6PErr#m-3UG`#Lm!6VVhb#KI3T4#,!!%+@i!#!!
',`a"lI8U,`K1Z[K++&p1ALkI6R919J!!,bhf0+N9,bhf0+NI6Pj1G8j@rr)[,IB
d2c`!!8KZrrj)E[rk5'lrmUQ0,blrqLmZ!!bTMbmYpM3r2!!#5'lrrNKZrrT)E[r
bUBd[,[rk,bi!#+Q26VVrTNjH)&p36dl36PErmLmYpM3r2!!"5'lrrNKZrrT)E[r
bUBd[,[rk5(S!0+Q2,bhf0$mm!!*)E[rq5'lrqNKZrr+TM5mZrrS[,J!)UBmr2!!
#UFK1Z[p36PiZRdje'8%JEQ9dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[lb,bhf0$m
m!!&)E[rq5'lrqNKZrr+TM5mZrrT)HJ"+UBm[,IBd2c`!!NKZrrj)E[rk5'lrmUQ
0,blrqLmZ!!`[,J!)5'lqmMmm!!*1Z[RB5'lqmUQ22c`!!UR)6VVqcNjH)&p36dl
3'8%JEQ9dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[lb,bhf0$mm!!&)E[rq5'lrqNK
Zrr+TM5mZrrT)HJ"3UBm[,IBd2c`!!NKZrrj)E[rk5'lrmUQ0,blrqLmZ!"![,J!
-,bi!#%KZr[)r2!!$6VVj8%KZr[+TMcmm!!+Tb%kkrNC1AL"Ih[`!$%l3'8%JEQ9
dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[m!-#i!#!4!rm0V!!(3$%!!('i!!FMM5$!
l!!C1q`!!!CS"[J$k!+B"[J'q!Ei"[J'q!Ei"[J'q!8`!8J#k!BB!d!'X!13"FJ%
N!@!!NJ'q!$`"%!%i!(`!D%(Zr`"$qJ0-F!BJf90!E[S`N@!!!@j"l[m!3rS$((!
')0P63'lk-*&J!!&B3Hlr!%2k!Zj`"L$C8d"ZqQ!!!84"l[m!3rS#b(!%)0P63'l
k-*&J!!%Z3Hlr!%2k!U3Jf5$C)0N`N@!!!4T"l[m!3rS#G(!()0P63'lkB!!""N(
Zr`"$qJ*1F!3Jf90!E[S`N@!!!2""l[m!3rS#*(!&)0P63'lkB!!!h%(Zr`"$qJ(
fF!BJf90!E[S`N@!!!-C"l[m!3rS"aR!')0P63'lk-*&J!!#`3Hlr!%2k!D!Jf5$
C)0NJf@!!!*a"l[m!3rS"I#$C)0NJf5$CB!!!L%(Zr`"$qJ&1F!BJf90!E[S`N@"
b3Hlr!%2k!44`#5$C8d"ZqM#4B&j"l[m!3rS!h(!*)0P63'lkB%a"l[m!3rS!V(!
()0P63'lk-*&J1%(Zr`"$qJ##F!8Jf90!E[S`N@!N3Hlr!%2k!%T`#5$C8d"ZqQ!
53Hlr!%2k!#*`"5$C8d"ZqM#4,bi!#N(Zr`![#%kkr3K1AL"IA%p1d"9)BA*NGf&
bC5"fEfaeE@8JE'pMDbiL8'9bE@PcFfP[EL"NEf9c)'j[G#"KE'a[Gb"hFQPdD@j
R,J!98fpQG(GKFQ8JGQpXG@eP)'a[BfXZ(&4[Eb"YB@jj)'CTE'9c)'&bC5"[F'9
Z)'j[Gbi!)e4SC5"RDACPEL"QD@aP)("[FfPdD@pZ)'Pc)'PZGQ&XD@3Z*94SC5"
QD@aP)'Pc)'&XFQ9KC(NJEh"PEL"QEh)JGh*TG'PZCbiB9'KPFQ8JDA-JEQmJFh9
MD#"fEfaeE@8Z!!p0C@e[FRNJDA-JCR9XE#i24'PcDb"*,dmJCA*bEh)Z'84TCQC
TBh9XG(NJGfPdD#"bC@jKE@PZCbiB9'KP)'CTE'8JC'pPFb"ZEh3JCAKTFh3Z!"0
8D'8JCQPXC5"TFb"XEf0VC@3Z%94SC5"QD@aP)'Pc)'*eFhNZ'd9iG'9bEQ&X)'C
TE'8JFhPcG'9Y)'9bFQpb,Ja&EQ3JEfBJCQPXC5i!%94SC5"NDA0V)'Pc)'CeE'`
Z&P4SC5"NDA*PBh4[FRNJDA-JCR9XE#i!'&4SC5"QD@aP)'&XFQ9KC(NJCAKTFh4
c,J!C9'KP)'CTE'8JEQ&YC5"TFb"TERCKE'PN,Nj@!!![,J!-3UG)HJ!@6VSE8#m
Z!!K1Z[Z@6PiJAe"26Y!81L"*)'0KELGd)'GPG#"K)'jPGb!!6PB!!#mZ!!a#TdK
k!"C1ZKXB,bi!#%kkqej1AL"I8%p1d#Bk)%NJBf&Z*h3JEh"PEL"K)'0[EQjPBh4
TEfiX)("bEh4[BfpX)!"19J!!,bi!#%+R5(S!1Nkk'Xj#TdKk!""1ZKV%6VVl$Nj
H,Tp1G4j8D'8JEh"PFQ&dD@pZ)(GTE'`JBQ8JB@*[FR4PC#i!*cSJ9'KP)'C[FQ9
TCfiJD'pcG#"TFb"ZEh3JFQ9cF'pZC'PZCbiJ)%j@r`")HJ"#,bi!#NKk!$4)E[m
!2c`!!dkkp%K"lI8d3qlr!("!)0P63'lk3Hhe0#m)2bi!#%kkqa"1AL"IA%p1d!3
L1b!J!!j*)'0KELGd)'p`C@iJ)J"19[m!5(S!3LmZ!!T)HJ!d5'lr!$mm!!01Z[2
`3Hhe0%2Zr`"`3#$C8d"ZqN(Yp63[#$mZ!!K1Z[Ui6PiJAea26Y!%)MXJ)!!555"
MB@iRG#"hFQPdC5"dEb!L!%j@r`")HJ"#,bi!#NKk!$4)E[m!2c`!!dkkmj4"lI8
d3qlr!("!)0P63'lk3Hhe0#m)2bi!#%kkqPa1AL"IA%p1d!3L1b!J!"0*)'0KELG
d)(*PB@3JCR*[E5!L6PErrLmZ!!T)HJ!b5(S!,NKk!#UTLd*R2c`"68+RUBBpArr
q%#i!#'F'6VSC9'!%6VVdZ%jH)&pF6dl3!!"19[rq,`Gq!@!1)!G"lINZk8"#-!!
18NF-4`!3Eq`Z(djH6R919[rd,`FJEJ!)3qlrpL,B)YJbN!"q!3a(!""I`'i5)JG
"lINZk8(!-"!1C`454f$Q$%F!%'ib)!G"lINZk8!KVJ!-!!!J"d(Yq5lT3%(`!!4
$l[rf)0NJf6#4)!G"lINZk8!4[!!"!!iZ(djH)&p36dl36PErrLm(IJ&J1L!(3Hh
j,ZP!%M!!$QFU)!G"lINZk8!L-!!!XUi!#'BB3UFJ"d(Yq5lT3%K`!!41ZKK5,9m
!$'!-8NF-4`!3Em"#VJ!-,Kp1ALkI6R919[rq,`Gq!@!`)!G"lINZk8!5-!!1Cb!
J"d(Yq5lT3#)`!!#bVJ!)CJiJ"d(Yq5lT3%)`!!jJ#&*($%F!%'r+,Kp1ALkI6R9
19J!!3Hhj),(YqN"R+%+R6VS$1#!IX+hk3&r!)'hk3#)YqN#bU!!+9X'!!@F),bh
k3%kk!M*1ANje6PB!!#"Z!!J4I!!"!!j1ALkI6R919J!!,bhk3%kkrq*1ZJ$+6Pj
1G8j@rqK)j`-B)'i!$%2ZrrBLf#,B-T!!-#i!%0"m!"!q!#!(!N!!!@F#8NFQEIN
`3SDCc#!,9X!L$&I"`!&R+VjV!!KZ(#K,2LX!#%U'CJJVD`!%q6"J%#"')@X!"!!
%B!BX#bCV!!4JbL!-CJT#Tcm(6VS#C#KI,`a)abm(,bi!%LmZ!!K1ZJ(d'A`!!3!
1)'i!&LPS!!3!"#"Z!"BK6!!%,`a)E[rf6VVpjMP(!!JT6!!+,8`!'NcI'-"1AL"
Ih[`!%Nl36PB!!%kkrY3VEIT!q63JEINd+fJ!"2Nd)'hj0#!Yq65`U!!+C`J[,IT
!6VS"&#"Yq64++!!1CpBJEINd3LJ!$L!Yq65`VIT!C``[,IT!,bhj0%kk!6`VEIN
dqN!3,INpCa4#,INp)'hj1#&Yq6!!"#YYq6Mj-%jH6R919[rm,``SEIT!)#`!",#
YqN"R"LKX!!4Jm#"YqN!TD!!%!!3EI!!"q6dVEIT!q6J[,IT!6VVpiNkkrda#TdK
k!"*1ZKB+3QG1Z[bN+&p1ANje)%4TFf&cG'9b1L"dDepPH'Pd+#NJFQ9dGA*ZD@j
R)C!$!%j@r`"#TdKk!%4#TbmYqN"1Z[dm5(S!)%KZr`!r2!!$6VV[UNKZr`"1ZK@
`3QG1Z[a+6Pj1G43JDA-JG(*jD@jR)(4[)(*PG(9bEJ!&9'&cDb"19[hq3UG1ZJ$
F5'lprNkklc![,IT!5'lqrNkklb4)E[hq5'lqrN+R,bi!#%kkr-j)HJ!FUBY#Ccm
m!TT#TkQ'29rrrNkk&@*1ALkI6R8!!%(k!#!JRb*251FI2L"T!!3JMb"4,P"-hhc
i8)mLHJ!%6Y%!N!4"qJ"!))iJE`!3dHm!$#%[!!4$q[mN)3NK,`!))3NX5*(m!*!
$&#%*)3P)i"mq)Qm!%#+))&rIr!#3!a!XHJ!%6Y!!N!3J$b"I3S%b(j!!J82k!!B
LJ%l3!*!%)&p#J$!I3rVrp*'4,VVrlNl3)&mL6bk*6Y"19[rq51F"##KZ!!K#Cbm
-6VVVE$iI3T3jEJ!@!!T#E!!-)!ab%Y#"+8!!"MPm,`d!%MPm3IJ!&$Pm#33!&MP
m+P!!'$Pm,c`!'LPZ!!`!($Pm6VN!)#PZ!"!!)MPm+Pm!*MPm6R8!+%*R,`a1ZZX
#2Kp-ha#!6PiJAplm!""1d%j@!!![,J!8F$`[!%kklVJ[,J!3,bi!$#mZ!!K1Z[p
L6PiJAplm!""1d%j@!!!JEJ!)5QJ!#QB'3Li!$'!D3QF[,J!)6VVUZ%TIC`C#,J!
-B!BGI!!"!!a1ALkI6R919[rf51F$##KYqTBJ$'F53QF[$%(YqT3[#%kkkZSm(f!
@ILT#Tbm(6VVU-#KI)!aQ"N+Z!!KJ'N+81A`!!3!%3Q`!#LPYqT!!!!iV62U3!#e
-!!K-ha$!6Pj1G8j@rrC)j`FB*Qi!#%*R,`Y1ZZSd5PpQ%%*R,`Y1ZZSD1Kp#,J!
-B'DhlIU3!'B)+fX!$[U3!'!S+'hkN!"q!8UX!!j@`#)(`J"R&VIX!!jQ#LPV!!i
!$N)(B!3SE!!1B0j#4LKYqTBJ$'F'8NBS9'$f$%B!(Q`1,`Y"lIU8,`K1ZZSHB!B
[#dkkkBBGI!!"!!a-haMJ6PiZRdje6PErqNMR!3JSEIU3!#!-Ca"#Cbm-6VVTQ$i
I+'`!$Q$X3UhkN!"-ha#!6Pj1G8j@rq`[$%+R2c`!(d+RF2m[!+Pm+&m[$$mm!!0
)E[ri5'lrp%KZrqbTM5mZrr4)EIJBUBm[$$mm!!0#Ccmm2S#TIN+R5'lrrUQ4$'i
!!IrqCJS[,[rd,bi!#+Q3!#m-UB--EJ!"rrjA`%3!(8!!$#KI6PiZRdje6PErr#m
(%#hf4dL!28$rr%*(B#SJ"d(YpNM"r!!+)M!!",+Z!!aQ%L!(3Hhf5-(m!!SKVJ!
)!!"JAP*(D3DqE[rmEp!3,IC'5)""lIC)`I`!#L'Z!!`!""!YpNC)J%(YpNM"r!!
+)Di!#!!!%#hf4NL!8N!E32C'$#d!#[C'E`4#,IC'$#d!#[C(C``3,IC(5)"53"Y
!pNFZ(djH)&p36dl36PErqNMR!3JSEIDf1,`!!cPm#!!!!R!%'8!!"(!%'8!!"6P
Z!"!!"N*R5'lrqNKZrra1ZZhQ5PpR!Q"k'flrqrE!1flrr2DqF%JE32E"+@hf[J!
)+@hj'!!-+@i!$!!3+@i!#!!8)'hhkL"3$'J!!3!#CJC1Z[PbB1`JEIIU+&!jI!!
A!!J3,IE"!N!!rcP!!!STEJ!-!!`jI!!B!"!TEIDf!"4#CbmYpqT#Camm!!&1ZZd
32Kp-ha#!6PiJAplm!!T1d%j@!!![$%kk!Xa1Z[R`+'hfaJa8!!0R"P*YpMKJk!a
X#!!!!QF'8Qhf1'$D)#`!&,#Yq4KR"P*YpMjJbLmX!!`[,!!)6VVq9$!X!!C63'F
'8d"R''!`8Qhf3Mmm!!)[,!!),b`!$%kkrY4J(NUYpX*R#LmYpX*1Z[LBB!45EIB
k3Uhf`Q!%8Qhf2'!!rhSSAdjH,Tp1G8j@rr`["a!YpNG)J$e!rra#4f!b)!G"lIC
)`I`!#L)`!!5bVJ!-CKSJ"d(YpNM"r!!+)Qi!##+`!!!GI!!"!""J$P*(D3DqE[r
mEmK#,J!3,Kp1AL"I8%p1d%j@!!"+VJ!)9X"R&L)Z!!LbVIT!9X(!!@F),bi!#%k
kq!"1ALkI6R919[rk51F$##`Z!!a#Cbm',bi!#%kkrfJ3(fF%B!!!eLYYqN$f`Mm
m!!%[,IDk,`C1Z[hb3UG1Z[Zf+&mJ$'BL)'i!#%*3)'i!#(!!%8!!!b"Z!!K`!"&
!!!*#VIE#B!!!P#mm!!!$K%(krfi[##mYpX)[$%kkqTT1Z[KU5Uhf`QC'3QF[$%k
kqc)H(d*R,`B[,J!)6VVqk"!IC`j#Cbm-6VVlTKiIB%aJ5#mm!!!$K%(krbB[##m
YpX)[$%kkqP)VEIT!pX*J+N*R,`a1Z[Yk(KmJEJ!)3P!JEJ!)F!!43!!$)'i!#(!
!%8!!!N+YpX*J!Q#%60m3`%jH)&p36dl3$!!!5'F%B!!!TNIU!!%-+`!"!!*@LfB
'%#X!"'!%%#X!$!`!!"CQ8NIk!-a+NfB53N01V!!#6VS!pN(k!,K58%je+RS!YMY
"!!if2!*B*RS!TNkX!!*"qJ#H3T!!3IS!R#m36VS!bNEU!"`JAd2i!@SK83!+6VS
!T%je$!!!&fF#B#a(qJ"#5K0Q!Q!L0M`!'%Ik!1"1V!!#5S0R!Nje3IS!*N)34ZS
!(%kk!'41G8*$6U`!!N(k!$j58%je3IS!#"#m!!&1G3!!3IS!1##[!"""qJ!i)+m
!$%(k!#3JV`!)3IS!*##[!!4"qJ!+3P"1ZJ!q6R8!N"T"q[rk,a"1Z[AL6R9$q[r
L)P'TEd(krpi[%%kkpFj1G5m-3IVre#*35UN!!QFF)'N!!LK)U@j+3'B33IVrX##
-3IVrTL#X!!CJ$%(krk"#N!""q[q@3T!!+&p1G8(k!!J[5!!%6R8!N"K"q[pS2e!
!"%je6PErh%MR$aJQEJ!13UF[#dkk"2BX(d+R,`C1ZJ8!+&mi,!!#3Q`!!K!Z!!d
#3!!"Ca!Y62rH)'lrhM!Z!!a#-!!!3UG#Cbm-3S!b,J!-8N%`!H+)2`"1ZJc'-"p
)`#m!U&SJ(ce!rqkiE[rZC`ij4!!#,`Y1ZJ@8B!!"JMP%!!)3&!*!!2pA3'F398"
R!!#888"R!!%)B!!"AN+R3UF[#dkk"'C1ZJ4f,9rrm%*(B#iJ"d(YpZER3#*Zrr!
L-!!!XUN!''B@)!G"lIEQjd"#X!!%,`Y1ZJ8dB!!")P*($%F!$fr-)'lrm$!YpZ4
$lIEQjd!MU!!B!!!`,IEN3HhfjZG!3V!!"!aY!!rfj'B'3Qhfj'!%8Qhfj#m,6VS
%lQ!!!0`U$%*(B$!J"d(YpZER3#*&)M!!!,+T!"KQ'L"&)!G$lIEQjd!MU!!%!!3
[#dkk",TJ!!#S8NF-4`!2EmSJ46!YpZ4$lIEQjd!MU!!B!!!J46!YpZ4$lIEQjd!
MU!!%!!3-E3!2pZ4Q"N*YpZ4J"&*YpZ3[#dkk"(*JB(!1')"#E!!#3UG#Cbm-3S!
`2!!8iSJr!%kk#fJ`(dM!,`#S@L!I18!!!L"')NBMD!!3!!`J4L&Z!!J!%%*R,bh
fi#m,2c`!&#mZ!!K1ZJ6@5Pm[#dkk""KJ"Lm,6VS%%%cI'2"1AL"Ih[`!#Nl36PE
rlNMR"aK#EJ!53UFr2!)!3QG1ZJ-H*PmJ#fBJ3UG)HJ#i6VS+k%+R5(S!TNkk#Yj
1ZZpk3Qi!%Q!!!)K#Td+R,`Y1ZJ+i6VS#b#KIF!-BJ"PZ!!N!!8*(B"iX,J!+)!a
3J#S!)%8J"b*'%M&`!!*"!2m4J3!!8NF-4`!EEpa#E!!#3UG#Cbm-3S!`2!!NiSJ
r!%kk#Ri`(dM!,`#S@L!I18!!!N*R,bhfi#m,2c`!*#mZ!!j1ZJ2q2Kp+4bm,6VS
$2NcI'1"1AL"Ih[`!#Nl3"R"KBfYPG!!,58008&p%490898j19[ri,@i!$2ri,@i
!#2rm3UF3,[ri!N!!rdM!,`![2!#3!i#S@%UICM33,[ri!N!!ra)Zrr`#33$rXN"
A`")ZrrN#33$r&#lrr3*#!2qd39I"`!&%!"e!!""J!!$53UF3,[ri!N!!rdM!,`!
[2!#3!m#S@!bI!*!$J'C)%#lrq!*!!2m5,[rm!N%!rl*!9m!5,[rj!N%!ra3Zrrd
#3J$rY%&A`F!"%LlrqJ*"!2m8,[rq!N)!rl4"9m(!!83!(8!!%'"U3UF3,[ri!N!
!rdM!,`![2!#3!q#S@!bI!*!$`'C)%#lrq!*!!2m5,[rm!N%!rl*!9m!5,[rj!N%
!ra3Zrrd#3J$rY%&A`F!"%LlrqJ*"!2m8,[rq!N)!rl4"9m(!!83!(8!!%'!%3Li
!%%jH)&p36dl36PErrNMR!`JSEJ!),#i!$%*(B%SJ"d(YpZER3%U`!!"Q"'!iB$B
J"d(YpZER3,b`!!"Q+#!(3HhfjZG!+,!!"#!(3HhfjZG!5V!!"'B-)!G"lIEQjd"
#X!!!B#"54`a(!!p[X%*R,bhj'#m'6VVqA"!IC`3SKQ!%++hj(%cI%-"1AL"I8%p
1d%j@rrK)j`%B,Li!$N+R,`G1ZJ")*Pp#Tbm,6VS!8LKI%"3#3!$r$%!!#'BDF!!
BJ%*R,bhfcLm(2bi!$#mZ!!K1ZJ(35Pm["dkk!4*-haL!6PiJAplm!!T1d%j@!!!
JEJ!),@J!"J!-6PiZRdje6PB!!%+!)'i!#")3!N%!$c!"jBM3VJ!),8!!$%jH,Tp
1G8j@rr4)j`FB2#i!#%+R)!C@3%M!,`"#Th!$,`#S@UKB)"mm!#!'d(`!&$)Z!!V
53&*"2J%J"`*!!!&R!P0($'i#3!!+E`C#VJ!-B'3SEII`)!aR'%*R,bhhm%(Ypqi
[#%kkhX!k(dT&C`+Cc#!-CKj#TdKk!%j1ZJGL3UG)HJ!m6VS(@%kkkr4#VJ!-B#*
#Tbm-6VVr0LCI)!C)`)(m!!4D3!*!!!m#%rr`J4-Y6!!-60mBi%jH,Tp1G3C`B@0
VCA3!#%P1Ad&-6%p$!%j@!!![,J!)3HhhlLm)6VVH0NjH,Tp1G8j@!!!YEINB!!a
1ALkI6R919[ri51F"#$!Ypk*63$e!rrK#4f!H)!G"lIHNj8!JF!!!-"#`EJ!-CJC
#VJ!1B&*54fN'[Qlrq'rF$'d!#[HLCJ5Cc'!5-#hhSN(YpfE"r!!'3I!!!#K))!a
Q"N+Z!!jJ)$LZ!!`TEJ!)!!)`,IHL3HhhT19!)B`!!&*Ypk)Y6!!160m3J%jH)&p
F6dl36PErm%MR!aJ-EJ*B!!a[#Memrrm!&Q!!!6)[,J!)3Hlrr#m)6VVp3%UZrra
Q#%*Z!"CJ!!%@3UF[,J!16VVq#LCI!P-2r`"63!!`2!$r&d!!#!*V(rm!"J*Vi!!
!"MGYpm`!"&*Ypma#D`!+*fhj'!!-*fi!#!!33S!5%`*"!!m`!H@)-Li!$%M"dS!
q!6G(!!*`!"G!!!%JEJ!5&fJ!!3!*3UG#Cbm,3S!5%`*"!!m`!H1)2`"1ZJ@Q-"p
)`#m!U&SJ(cG!!!T5EII1,blrr%KZrr*1Z[A@%#lrp!*!!2p+3'B)2AcrrJ!@B&i
JEIIU)&!-D!!"!!*Q"NkklEaJl#"YpqSS8$Pm!"B!#$Pm!%J!#LPZrr)!$$P(!"!
JEJ!1+@J!"J!83QF[,IIU3QFI2!!"6VVKAM`I5NCQ#$em!!%!&Q!'2Acrr3!@60m
B`%jH)&rHr!!16Y"19[rk51F"#%+R,bi!#%kkr1!SAb"Z!!J`+!!1X'`!!Q`-8Qh
hi%)Z!!aJ!!#B%"6S5!*!!!m-3!!%C`T5EIIB3Li!$'"q2L`!#N*X!!T)ad+R3QF
[$%+!%K3#33!2-!(ML$m!6VS%S$!I5-![!+KD[TpR$MP(!!T5EIIL3Li!$'"#)#`
!%,#Yq4KR"N)Z!!aJ-M!X!!B#3"rr5N"@`")X!!EU53*"!!F#33!"J!%#3!!"C`T
5EII83Li!$'!'(A`!!3!-60m3J%jH,Tp1G8j@rqT)j`mB6VVX1NUYq!jQ"%kkl9B
SEIJ1)!aQ"P*Ypp*Jk%*R,`a"lIJ-,`K1ZYY+5PpR!Q$88QhheN*R,`a1Z[l`%"m
+!!!"C`a5EIIN,`a1Z[c3B,4#Tbm-6VVla#CI1#X!!N)&-#hhSP0!28$rkN*(B%i
J"d(Ypk6P3#``!!!J4K!V!!N#3!$rX&"Q-#"'5UJ!!QB%B$"J*#m-)!53!(`!&$m
!,bX!$#"',bJ!!Nkk!hK1ZZZ1HJ&1ZZ[@B!T54fN'[QlrkQqX)!8+!!!"Cb*#Cbm
V!!`[#cmm!!*1Z[K%29rrp&*YppT5EIIN,`a1Z[`bB!$r&NcI'2"1ALkI6R919[r
q3QFr2!")6VVI@MeIrrj1ANje6PB!!#m-+'i!#$!Z!!a)`#m!5'cr!%kkh,S[,2l
m2bi!$NKXr[T)E2lf5'cqlUQ0,bcqpNKXr`#TMbKI6PiJAe"26Y"19[lS51F"#%+
R2c`!68+RF2m[!+Pm,9rqr$mm!!%r,II@,`j1Z[q@2c`!!MmYpmi[$NkkriJr2!!
$2bhhj#m16VVrHMmm!!3r,IIL,`j1Z[pX2c`!"6mYppS[$Nkkreir2!!'2bhhf#m
16VVr8$mm!!Fr,IIJ,`j1Z[p#2c`!#$mYpp`[$Nkkrc3r2!!*2bhhe#m16VVr*LK
Ypr"#4b!-C`C54bK8B2Br2!!+2`F[$Nkkr`Sr2!!,3QG1Z[6i,`j1Z[lk,blqr%k
ki0*-ha#!6Pj1G8j@q[`YEJ!-rra#Ta!Zrr`#3!$r5-![!#mm!*!$rkKB5'llr%k
kfj4#Ta!Zrrd#3!$r5-![!#mm!*!$rkKB5'lmr%kkfhC#Ta!Zrri#3!$r5-![!#m
m!*!$rkKB5'lpr%kkfeK#Ta!Zrrm#3!$r5-![!#mm!*!$rkKB5'lqr%kkfcSJEJ!
),`K)E[[m5(S!1%KZr2a)HJ!`5'lpr%Kk!#K)E[lm5'lkr$mm!!G1ZYY')&p$l[V
mF%!Jf90!E[T1AL"I8%p1d!%Z6PErr#m(3UF[,J!)6VS!+#iI5SGQ$%+R,bi!#%k
Y!b)Z(d(Y!b)[#+Ra,8F!$#iI6PiZRdje6PErpNMR$`!JEJ!)%"!#3!$r2J"+4fB
)3Ui!$'!!!-3JEJ!)F!%5-!!!!N%!rfXBXR`!2fi53IS!a$!"jNK%3!-`!2m+2!!
%CPK#"!a(!!KX"$S(B!*k#$e&rrCm!Q!B)'i!#"!`B!!#3!$r$%!!,QB#H!&54QN
'['lrpQrL%!4R%%+R,bi!#%kY!cSYA`!-B&"#TbmZ!!K1V3-U,9m!$'"!)'i!#(!
"%M!!!!*"!2m-33!M9m!JEJ!)FJ%8-"!!!N)!r`a#!#4A`B!"Ca"#TbmZ!!K1V3-
b,9m!$'!%3Ui!$%(Y!c)[#+Ra60m!m%jH,Tp1G32r!*!')'m!"#kI6Y!JAbkI6Y!
LAd+"-KmJAe1"3S$3@'3#8S"4bIri2S"1d5"i!Ul3r!!+6Y!!!$)H!Y!!"8j@!!"
"lI8d3rS"BL$C)0N`,IUi5-![!%KYrX"1V3'k5'hq`%KYp633,I8d!N!!re*!2`"
1V3,D8QhkZ%Kk!5C)EI8d%#he0!*!!2p53$m!6Ud#fLmZ!!j)EIl!6Ud!FNKYrX"
)EI8d%#he0!*!!2p53$m!6Ud#fNKk!1C)EI8d%#he0!*!!2p53$m!6Ud#fJaZ!!S
!#'BD5(S!ZNKYp633,I8d!N!!re*!2`"1V3,DB$S-EJ!,!!KQ'NKk!)j)EI8d%#h
e0!*!!2p53$m!6Ud#fQ!B5(S!C%KYp633,I8d!N!!re*!2`"1V3,D,bi!#NKYp63
3,I8d!N!!re*!2`"1V3,D3UG)HJ!D6Ud#BN(Yp63[#%kY!B*1AL"Ih[`!#Nl3&e0
dBA*dD@jR)'CTE'8JG(*KER0QCA)Z$b"#383JC'PbC@0dD@pZ)!K6C@jND@jR)!!
+8Q9MC@PfD@jR)!!#1L!!"b`JGfPdD#!'G'CdF#!M!%j@!!!["ciZ!!J-4`!,9m!
5,IrZ#J%!!F!"$%F!#PI"&#hrl`S#!!(#!S!"C`!!ZN(Yp64$qJ%')0N`N5mZ!!j
)EIl!6Ud!FNKYrX")EI8d%#he0!*!!2p53$m!6Ud#fNKk!-j)EI8d%#he0!*!!2p
53$m!6Ud#fJa(!!TQ'NKk!+T)EI8d%#he0!*!!2p53$m!6Ud#fQ!H$%F!#fBB5(S
!K%KYp633,I8d!N!!re*!2`"1V3,D,bi!#NKYp633,I8d!N!!re*!2`"1V3,D3UG
)HJ!f6Ud#BN(Yp63[#%kY!B*#EJ!5B"3[,J!1,bi!#Mm(6VVpQ$em!!%!%LiI6Pi
JAplm!!T1d"T5C@TPBh4PC#"dFQ&ZFfCPFL"bCA&eCA0d,J!%Cf9d)!!%F(9d)!!
,1L"8FQPPC#"dEb!&4R*[E5"19J!!5Qi!#'FN3Hhe0%2k!$T`"5$C8d"ZqN+R5(S
!'%kY!Q*"lI8d,`K1V3'#6PiJAe426Y!64QPXC5"dFQ&ZFfCPFL"NEfjP,K06G@0
MCA0cCR9X)(4bB@jcCQ9b6PB!!%(krPS[#%(krjB[#%kk%'C1ZK"8%#hq[fF'6Ud
#LQ$d6Ud#FNjH,Tp1G8j@rri["hi"B!`[,Iri2`G#CkP&8NF-4`!%Eqi`,IUb8d"
R$P0!CaT93'FQ8d"R-Q!q,bhrq$mm!!%I2!!"U89J,LmYrrJr2!!#(c`!!DP&B"i
[,Iri2c`!!amm!!'T4@!1,bhrq$mm!!3I2!!"U88Z(djH6R919[rX,``EI!!"rqj
#,Ir[3UFr2!!b3UG`rbm!UA`SAbm-2c`!!dKZrrK)E[rd5'lrl+Q0,blrp$mm!!'
TBbm-2c`!"%KZrrK)E[rd5'lrl+Q0,blrp%*RU@0#TdKZrrkTN6!ZrrjV',"m!!G
Z%N(k!*Bb!1C*4%%"-"$r#M`!"'GN,``r,[rq5'lrq%KZrr4)E[rXUBd`,[rq9d"
R"P0!CajJ0K!Yrqi+!!!"'d$rlLmZrr33,IrZ5)!r!+PMB"S3,Ir[#J!!!4Y!rqm
[,[rd%#hrldL!2`#TBd+R5'lrrUQ4B!$rILm-UB--EJ!#rrjQ"N)Z!!KJ"Kem!!%
!##KI6Pj1G3!'6PErrNMR!3JSEJ!)-#hkXP0!C`j63'F198"R$P0!C`jJ$Ri&B!T
q"Q!'IJGJ!Ri),bcqqMm(5'cqq%KXr[4)E2lXUBd[,2ld2bi!$+PM60m3J%jH)&p
F6dl36PEpl#"Z!""$l[m!F%!Lf&0!E[T)E[m!5(S"b%kY!XS3(fF85(S"eNKk!EK
)HJ'd5(S"X+Q,B$SJEJ!-,`K)EIfm5'lr!%KZrH`r2!!#6Ud#dL"I3qlpl("!)0P
63'lk5(S"JNKk!Aa)HJ&i5(S"G+Q,3UFr2!!,3UG`rbm!UA`YArlk,blqqMmm!!4
)E[li5'lqp%KZrZbTM5mZr[4)EIbmUBm[,[lk2c`!!dKZr[K)E[ld5'lql+Q0,bl
qp#mZ!!bTMbmZr[Sr2!!$3QFr2$k!UAir2!!",`j1Z[l+3UG)E[lqUC%`,[lqDaL
`I!!(EK*"qJ$Z-J$Q584"!6!3r`Sm!!4RD%*R,`j1Z[kD-#lqrPY!C`j63'F58d"
R&P0!CaTJ(MYm!!(kXQ!@1h`!![UbB!ilI!!%qV*J"MYm!!AkXLmZr[Sr,[lq5'l
qq%KZr[4)E[lXUBd[,[ld2c`!!DPM3UG)E[lqUC&J!2pk$'i!![lqCK![,[lkUB0
1Z[bX3Li!&'"3,blqqMmm!!4)E[li5'lqp%KZrZbTM5mZr[3[,J!)UC!!,blqqMm
m!!0)E[li5'lqp%KZrZbTM5mZr[3[,J!-UC!!,blqqUQ$6VVmA"em!!%!&%jH)&r
Hr!!-6Y!!"J!!&djKE@8JEfBJ9'KP)&*PE@pdC5"'D@aP&8jKE@8JEfBJ9'KP)%4
TFQ9MG'pbH8j@!!![,IUS6Ud#JMYZ!!MkS%jH)&p86dl36PErqNMR!`"#TdKYr,a
1V3)D+erkY%UYqV4Q'%+R5(S"G%kY!Q*"lIbm,`K1V3#UB!!!rJbY!*!$!IUdCK*
#TdKk!6"1V3*L6Ud!XQ!!!1)[,IUd3Hhk[#m)2bhkTNkkq&a#TkPe,Kp#TbmYqV4
"lIUm,`Jr,IUk3Hhl[#m)2bhkTMmYqV*"q[pH,`K1ZK*k5TpQ#%*YqU"J!!#@6Ud
#DN+RUA8J(j!!Kbm!F$`[!%kY!VSZ(`aY!!VkTQB13QG#TcmYqVT1V3$#2"p+EIU
JCf""lI8d3rS!KR!&)0P63'lk-*%["dKYrX"1V3'k5'hq`%KYp633,I8d!N!!re*
!2`"1V3,D5(S!6%KYp633,I8d!N!!re*!2`"1V3,D3UG)HJ!B6Ud#BN(Yp63[#%k
Y!B*-h`$!6Pj1G4P'D@aP)(4bB@jcCQ9b)(0eBf0PFh0QG@`Z#5"cC@0[EQ4c,K4
'D@aP)(4bB@jcCQ9bFQ9N)'PZ)!!K9%C88$SJ6Q&YC5"cCA*fCA*c)'j[G#"bCA0
`EfjND@jR)94'9&!k)%0[G@aNELGd)(*PFfpXGQ8JD'pcG#"ZB@eP)%j@rTT)j`%
)2A`!C2rq2A`!@[rm3LhkT8kY!QSEI!!"qU8[,Ird2c`!!DNk,bhrp$mm!!+T1Lm
Yrr3r2!!%U6Nr2!!,UANr2!!aUAP1V30#$'d!#[UQCQ![,[rm5(S"V%KYqVa#TdK
Zrl*1V3*5%#lrXQG!3Hhk[%2Zrla`%#$C8d"ZqN(Yqla$l[qmF"!Jf90!E[SlE[q
iqVT#CdKYqVa)EIZm5'hm[%kkqh33(fF%6VVpQ'!!!4T#TbmZrra#Tcmmrrp)E[q
L3UG#Th!",`"`!Lm!U&XJ(cm!6Ud#5LKI)!aR!!$S)&3-8!!"CNiJ9#"S!#!J8%2
YqVa`3#,B8d"ZqL"8)'J!)#"33qhl[("!)YK63'lk)&3lD!!#qVT#CdKYqVa)EIZ
m5'hm[%kkq[!3(fF%6VVp&'!!!)a#CdKk!-K)EIfm5'hm[%kkqY)3(fGd)&3`%&0
!28$qQN*(B'!J9#!(`I`!"L"`!#!J8%2YqVa`3#,B8d"ZqNKYrE`J9#!(`I`!"L"
`!#![%%KZrT`r2!!#6Ud#dN(Yqla$l[kFF%!Jf90!E[SJ9$YS!!,kZNkkr*K+EIU
JCJ*J#P*(D3DqE[kDEjS[$%kY!)*1V3*#2c`!#kPk2c`!-DPk,bhrp$mm!!'T15m
Yrr3r2!!#U6N[,Ird2c`!"+NkB!$q'%cI%)"1ALkI6R8!!""-Ef0KE#"'D@aP)%j
KE@8k!%j@!!"#Td+R3UF[,J!)6Ud"5NkY!hT1V30U,9m!$%jH,Tp1G8j@rrj#Cb"
Z!!J[+!"+3QG1V3(L29rrrNTZrrjR#"em!!%!$'!8)'i!##"S!%T+U!!F9m"%!"e
!!!a1ALkI6R919[rk51F"'#CZ!!JSEJ!-3NGJ)NS8CKS[,J!-)!Y5J#m!5-F["dk
Y!$)S5aL(B!jJ!P+-8NF-4`$rEpK#%dcI')"1AL"I8%p1d%j@rra)j`%B*Qi!$#i
Z!!JJ#e+!+%![$#m(%"-#3!$r5-![!%kY!$)3%`*!!2p)`0k!)%G#%%cI')"1AL"
I8%p1d%j@!!")j`!B+'i!$#CZ!!K+8fC13QF[,!"+3QG1V3(#0Tp+8fF%B!!"IN(
X!&)LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'N*R,b`!5N*R6Ud"dMDI5P0R"'!
!!8Sf[2r9$&2re@B!!4T"l!"5)Q`!5L0)!")JE!"+-@`!8!!@)'`!5N)S!"T#Cbm
X!%T#CdkY!FSfRdT6CJ!!N!""l!"5)Q`!5L0)!")JE!"+-@`!8!!@)'`!5N)S!"S
JE!"+,`K#TkPe)"mJAb&!!%JJE!"+,`K#TkPe)"mJAb&!!%`JE!"+)A`rN!3!*#"
X!%T#D!!S)'`!5N*S!#i-,!!"!%jQ$L"X!%SKI%&38%`!)'!-)'`!5L&m9%9B9!!
J3QF[,!"+3QG1V3)#0Tp+8fC83H`!8L*X!%SM5!!5)'`!5M&X!&!!&L"X!%T#+!!
D)'`!5K&m!!-!'b"X!%T#U!!F5L`!6QB33QF[,!"+3QG1V3(b0TpJ$N*R,b`!5N*
R6Ud"kMDI5P0Q)#"X!%SaI!!"!#`JE!"+3UJ!,N*R,b`!5N*R6Ud##MDI60mB!%j
H)&p36dl36PErr%TZ!!aR0N+R)'i!$NKS!&*1V3*L2bi!$%kY!-T#TkPe+erkDNU
Z!!KR##mZ!!K1V30b8fhkEN)Z!"*J"Kem!!%!%NjH)&rHr!!+6Y"19[rm51F$##K
Z!!SjEJ!)!"!-,!!"!%jQ!!$33QF[,!"+3QG1V3(#2Kp#,!"13H`!8L*X!%SM5!!
5)'`!5M&X!&!!&L"X!%T#+!!D)'`!5N+S!"`-E!!+!#CQ'L"X!%S4I!!$!"Y#Cbm
X!%T#CdkY!I)q(f!m)'`!5K&m!!%!'d*R,b`!5N*R6Ud"mMiI5NGQ)#"X!%SaI!!
"!#`JE!"+3UJ!,N*R,b`!5N*R6Ud##MiI3QF[$$m(3UG1Z[ld("mJ"JS!!!&R'Lm
X!!B[,!!+2c`!!8+R5(S"#%kY!Q*1ZJ(X(8B!$Q!!!-S-,!!#!%jQ!!$!'A`!!3"
13H`!8L*X!%SM5!!5)'`!5M&X!&!!&L"X!%T#+!!D)'`!5N+S!"`-E!!+!#CQ'L"
X!%S4I!!$!"Y#CbmX!%T#CdkY!HSq(f!m)'`!5K&m!!%!'d*R,b`!5N*R6Ud"kMi
I5NGQ)#"X!%SaI!!"!#`JE!"+3UJ!,N*R,b`!5N*R6Ud##MiI3QF[$$m(3UG1Z[i
S("mJ"JS!!!&R'LmX!!B[,!!+2c`!!8+R5(S!'NkY!Q*1ZJ%J(8B!$NcI%-"1AL"
IA%p1d#&$Eh9XC#"ZEh3JBfKKEQGP)(4[)(*PFfpeFQ0P)'C[FQXG3fpeE'3JEQp
d)'0SB@jRC5"dEb"NBA4K)'C[FQY19[rL51F2'$JZ!!K#Cbmk!,UTR$`I3QG#Tcm
%6Ud#1NTIC`4J!!#B3QF[,J!+UCFq(`a(rrpQ"'!!!)4#Cbmk!)UTR$SI[%9XF%+
R,cS!I$mm!!'TR5KI)!aA`'FH,`"#CbmZ!!Sr"%KZrr"1V3%#-KmJ(dT"9X'!!@F
'2`HTQQ!m*P3Y8rrd3UF`,[ri5-![!#mm!!!J!+KE)"mp32ri3QF[,J!+2`4)E[r
`6Ud#+NTIC`Br"kQDB!3r"kQD60mBm%jH)&pF6dl33Nj%6%j@rr4)j`mB2#i!$#S
Z!!Kq"%+R,bi!$NkkqLJQAcDm!!8h4J!#5NCQ)#K&,`8J#eL!,`"1Z[Uk)%83%!*
!!2r34e*!2J"J!!#q-!CV!!#8$%!!"fi!!)cM5$!l!!C1q`#3!a)!)!!Z!$`!5J"
B!'B!G%+R5(S"1%kY!Q)SAf"Z3UG)HJ%D6Ud#BLKIB'"#TdKk!2T1V3*L+&pJ8N+
R5(S!iNkY!Q)SAf"%3UG)HJ#m6Ud#BLKIB$C#TdKk!*T1V3*L+&pJ+%+R5(S!H%k
Y!Q)SAf!D3UG)HJ"F6Ud#BLKIB!a#TdKk!%"1V3*L+&m[$#!,@)![!%kkqIJ3&!*
!!2r34e*!2J"#CbmZ!")[,J!12`G1V31D1"p-haM`6PiJAplm!!j1d!e9EQYZEhG
Z)'9bFQpb$%j[)(0eBfJJGA0PFJ!64QPXC5"KE(*PB@4j)'9iDA0dFa09EQYZEhG
Z)(4bB@jcCQ9b)%P%&NPXE'9RB@`J9%C88#"[F'9bBA4TEfi!#84TFfXJCR9XE""
"Bf0PFh-JGQP[E'&dD@pZ!!j'D@aP)'j[G#"QEh9ZC!!B9'KP)%T13b"0C@e[FQP
KE#"#98G)38a8!%j@rrj)j`%)+'i!#%*R,b`!'%kY!TSH(cPm!!-!(#mX!"41V3+
#60m3J%jH,Tp1G8j@rrS[$#KYqP3J$'Fk$'`!!3!XE5![,!!',b`!#N*R3UG)HJ"
B6Ud#BNkkrG`[$%kkrjjJ$N+R5(S!&%kY!Q*1V3#b+&4J`LKI6Pj1G5a6Eh*bH5`
JH@pe)'0KELGd)'&LEh*d)'%JBfpZEQ9MG'P[EL"bCA&eCA0d,J!53fpZEQ9MG'P
[EL"KBQpbG'9N!%j@rra#TbmZ!!K1Z[HZ,9rrr#!ZrraBJ#e!!!a1ALkI6R919J!
!1h`!!ITF6Pj1G8j@!!"#EITF3UhkDN+R3UG#Ccmm!%9"qJ"1,`K#TdkY!i)VArT
H5UhkAQB33UG)HJ!J6Ud#BN*R6Ud!ZLYZ!!MkCLYZ!!ckBNjH)&p36dl3&80KELG
d)'p`C@iJ9843)(0[BfYPG%j@rG*)j`mB*Qi!%NkY!'*#Td+R,`Y1V3&+6Ud$HLe
IrGj#TbmZ!!`JE[hH2a"1V30D5TpR#Lm,6Ud$FQ!!"@!J,ITUX+X!#Qm+,`Y1V30
bB!!&6!aY!!(kEQdX6Ud#LJaY!!(kEQdJ,bhkALm,3QG#TdKk"G"1V3*L6VSFM#m
,6Ud$FQ!!"4K5EITZ3UF[#dkkpT)Z(b"()NFbN!!J4`a3!!*[$Lm,6Ud$FP0YqQj
J!!6Z)!G8J#m!3HlqmLm)6VVfb#!(9)!5,[lb!N%!rdM"dS"5J5m"3HlpmLm)6VV
fUN)'3QG)E[hb5(S&9%*R(c`!!8kY!+)3(fF'HJ*m!@"m3QG)E[hb5(S&-%*R(c`
!!8kY!+)3(fF'HJ*m!@"H3QG)E[hb5(S&#%*R(c`!!8kY!+)3(fF%HJ&J3N*R5'l
pmNKk"1*#Camm!!&1V3#L%"pR"RS&I!*J*#mYqPi[#d*R3UG)HJ5f6Ud#BNkk'j`
[#dkY!h*6EITZB!!%*%TYqPaQ*#mYqPi[#d*R3UG)HJ4U6Ud#BNkk'h)[#dkY!h*
6EITZB!!$qL"($&!!!@B%H!YJ!RJ+3QF[,J!-3HlqmLm)2`3[,ITL6Ud!3NTICL`
[,ITH,`Y#Cd+R5(S%$%kY!Q*1ZKXQ3UHTG5YIqQS[#dkY!h*6EITZB!!$TN+R2c`
!#cmm!!&1ZKAH+&mJ$'BQ3UG)HJ2#6Ud#BN+R5(S$[NkY!Q*1V3"5,`Y1V30b8fh
kEQ!!!f`TEITQ!"j"l!"53qlqmR"!)0P63'lk'8B!6N*X!&!j43!N3UF[,J!-)'l
phMm33QG1V31+3ISBkLm),`a1V31#+9m!"NUX!!CQ-N+R5(S$8NkY!Q*#TdKk!d4
1V3*L6Ud!@N+R,`a1ZKFf,9rpe#m,6Ud$FP0YqQjJ!!,`)%F-8!!"CJ!"JMPm!!X
!*MPm!!%!%N(X!&)LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'L"X!%S4I!!"!"X
JE!"+3UJ!("!X!%j)J'F18d"R@&0!C`!!SQ!!!-4#CbmX!%T#CdkY!I)pArh55Ql
pdQBf3QF[$%kkp#!3(fF)2AcrfIh5B#)JE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T
#CdkY!JSpArh5B(4#CbmX!%T#CdkY!HSpArh55QlpdQBf3QF[$%kkmp)3(fF)2Ac
rfIh5B%SJE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!JSpArh5B#C#CbmX!%T
#CdkY!I)pArh55QlpdQB33QF[,!"+3QG1V3(#29rpdN*R,``r,[h5,`Y1Z[AX%"m
+!!!"Cc3[,!!',b`!#Mmm!!&#TdKk!Ej1V3*L6VVijN*R,b`!(NkY!$T#Tbm-6VS
9a#eIrG4J!!')3UF[,IT!3IS*a#m)2c`)!%Kk!D![$%kY!RSTA`!8B!!"B$Pm!!S
!*MPm!!%!%N(X!&)LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'L"X!%S4I!!$!"X
JE!"+3UJ!("!X!%j)J'F-8d"R*&0!CcaJ!!#B3QF[,!"+3QG1V3(b29rpdLm-5'l
pdNkkmh*JHN*R,b`!5N*R6Ud"kMeIrG)[$%KZrG*1Z[0@B&j#CbmX!%T#CdkY!I)
pArh55QlpdQB53QF[,!"+3QG1V3(#29rpdQ!f$'lreIh5CLj"l!"5)Q`!5L0)!")
JE!"+-@`!8!!@)'`!5N)S!"T#CbmX!%T#CdkY!FSpArh53QF[$$mZrG)[#dkkp*S
3(`S!!!&R-LmX!!B[,!!+2c`!!N+R5(S!E%kY!Q*1Z[H83QF[,!!H6Ud!1N+R,`a
1ZK4b,9rpe'!f3UF[,IT!3IS'YLm)2c`)!%Kk!$)[$%kY!RSTA`!86Ud#LMPm!!%
!%#m-3QG1ZK&Z,`Y1V30b60mBm%jH)&rHr!!16Y!%G'ChFJ!A3fpeE'3JEQpd)'p
`C@iJG'KP)'CTE'8%G'CbC!!$9843"&4'9&!!#Q0[EQjPBh4TEfi!%94bB@jcCQ9
b)(*PCR9cC@3Z)94bB@jcCQ9bFb"KFQ8JEQpd)'*PD@jR)'&MBf9`G'9N,JK#B@3
JE@pNC3!*E@&MD@jdEh0S#'jPG'&cBfPT!!9TE@&RC39[Bh4PG"98EfmJE@&ZH5"
MEfjZC@0dD@pZFbj19[rU51F2#$`Z!!a#TcmZ!!ir"Nkk%A3SAb!-CL"#TdKk"Bj
1V3*L3UG)HJ@86Ud#BNkY!&*#VJ!HB!!&&P*YqQiTEJ!)!"iJEJ!@3q`!8R"!)YK
63'lk1@i!&!"318B!*!a'!!9Q#"Pm!!)!6Q!U-!CV',"m!!GZ%N(k"83b!1C*4%%
"-"$r#M`!"'B)'A`!!3"1B!4#,!"1$'i!#J!1CJ!"p$!'DaL`I!!(EK*"qJ8)-J$
Q584"!6!3r`Sm!!4Q!!$H3H`!8L*X!%SM5!!5)'`!5M&X!&!!&L"X!%T#+!!D)'`
!5K&m!!-!'b"X!%T#U!!F%#`!6NL!C`a63'FN8d"R2'!!!+a#CbmX!%T#CdkY!I)
pArrU,`a)E[rU6VV`QQ"k3QF[,!"+3QG1V3(U29rrkLm-5'lrkNkkm(jJFN*R,b`
!5N*R6Ud"mMeIrqT+E[rUCK*#CbmX!%T#CdkY!F)pArrUB%S-E[r9rqTQ,N(X!&)
LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'N*R,b`!5N*R6Ud"bMeIrqTJ%N+R,`a
1ZK(5,Kp#VJ!HB!!$SNTZrqTR(LmZ!"Br,[rU6Ud!bN+R,`a1ZK'Z,Kp#VJ!HB!!
$IN*R6Ud$LMSI3UF[,J!D2c`!46m&3IS6$Lm),`a1V31#+9m!"NUX!!CQ+N+R5(S
$V%kY!Q*#TdKk!jj1V3*L6Ud!@N+R,`a1ZK&D,Kp#VJ!HB!!$+LPYqN!!&%*X!")
jI!!"!""#TbmYqN""qJ1+,`Jr2!J!5(S$E#m-6Ud#HLPI!"4#Cbm-,bi!%%kk#Gi
i(dT%E"j#TdKk!b"1V3*L6Ud!XN+R,`a1ZK$k,Kp#VJ!HB!C`!5e!!"jJ!!,#B!!
#[JaZ!!X!$QB!!V3`"QXBX(`!"fi53IS$$$)!jNP%33%`%2m+2!!%CJ!"0%(X!&)
LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'L"X!%S4I!!"!"XJE!"+3UJ!("!X!%j
)J'F18d"R@&0!C`!!SQ!!!-4#CbmX!%T#CdkY!I)pArrU5QlrkQBf3QF[$%kklF`
3(fF)2AcrfIrUB#)JE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!JSpArrUB(4
#CbmX!%T#CdkY!HSpArrU5QlrkQBf3QF[$%kklAi3(fF)2AcrfIrUB%SJE!"+-A`
!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!JSpArrUB#C#CbmX!%T#CdkY!I)pArrU5Ql
rkQB33QF[,!"+3QG1V3(#29rrkN*R,``r,[rU3UG1ZZqB%"m+!!!"Ca*#Tbm-6VS
2P#iI3Ui!(Q!!!@4J%N+R,`a1ZJq!,Kp#VJ!HB!!"8%TZrqTR(LmZ!"Br,[rU6Ud
!bN+R,`a1ZJpF,Kp#VJ!HB!!",%*R6Ud$LMSI3UF[,J!D2c`!46m&3IS3[#m),`a
1V31#+9m!"NUX!!CQ1N+R5(S"@NkY!Q*#TdKk!8a1V3*L6Ud!@N*R,b`!5N*R6Ud
"`MeIrqT#Tbm-6VS1q#iI3Ui!(Q!!!-JTEIT!!"4#E!!33Q`!%NTX!#*R8%*R,``
[,J!36VS(PMJI5N4X)N+R5(S!f%kY!Q*1V3#b3UF[$%kk$V)Z(d+Z!"jJ!!##B!C
`!5e!!"ijI!!#!"a1V3*U$'`!"!!FC`*J!Q#U5Q`!)QBJ3UG)HJ"X6Ud#BNkY!,*
#Tbm-6VS1ELiI3Ui!(Q!qB"J-E!!$!"aQ%%+R,`a1ZJj8,Kp#VJ!HB#4#TbmYqN"
"qJ*8,`Jr2!J!5(S!)#m-6Ud#HLPI!"4`!5e!!"j-ha$`6PiJAplm!"C1d!4dCR*
N!#C84P431L"5CA&eCA0d)(4[)(0PEQ3JEQpd)'&MDfj[GfaPC'GPC!!G9%C88$S
J4'9cG'PZBA4TEfiJG@jbC@&MD'&LE'8$9843"&4'9&!!!$B%G'ChFJ!!&!TMEfj
ZC@0dD@pZ!%j@rrK)j`F)+'i!#$Pm!!%!(!aX!!%!('B'6Ud#DQ$b$'`!"!!FCJ!
!LNTX!#*[1%*R,b`!"LmX!!Sr,!!16Ud$QMSI3QF[,!!B6Ud#QK`I,b`!1N(k"4J
[##m-,b`!'%kY!V*JTQ"+3UG)HJ&-6Ud#BNkY!E)-E!)!!#aY'#mX!!B[,!!+3QG
#TdKk!4"1V3*L6VV`+%*R,b`!(NkY!$T#Tbm-6VS0"LiI8fhkEQ!!!,S-E!!)!"a
QIJaX!!8!*&I!5L`!6PE"`!&R-MPm!!F!(%*R,``r2!!"6VVY&"!I#J!!!@F@3QF
[,!!H6Ud!1N+R,`a1ZJbf,KpJEQ!k3QF[,!!B6Ud#QK`I2c`!!5mX!"j1V3!k3UF
[$%kk$*!!,Kp+KfB13UG)HJ"36Ud#BNkY!,*6EITZB$)-E!!(!"aR*N*R,b`!'%k
Y!TSF(d*R,b`!(NkY!$T#Tbm-6VS-8#iI8fhkEQ!%B!$qT%kY!R*-ha$J6PiZRdj
e)P4bD@9N)(4[)(4bB@jcCQ9b)(TPFQmYE'9ZCh4S)'CTE'8!'e4[Eb"YB@jj)(*
PG(*TCA-X)'GTGQPZCb"eF!484P43!%j@rma)j`mB+'i!#%)Zrm`Z2!!!!J"#"%*
X!"!YE!!+rqT#Tcmm!J"#CdkY!e)QAb!,CL"#TdKk!fC1V3*L3UG)HJ0L6Ud#BNk
Y!&*6EITZ6Ud#FMPm!!)!("!X!%j)J'F18d"R#P0!C`!!kQ!!!43-E!!"!#4R4#"
X!%S[#%+R,`Y1Z[%#)"mJAb&!!#!JE!"+)A`!!!)!!#3JE!"+3QJ!,%*R,b`!5N*
R6Ud"qM`I)'`!5LiS!#KJ!!$),M`!!!)!3UF[#dkkm,iU(a!ZrmaR$N)Zrm`J44#
m!!T6Ke+&)'`!5M&m$B!!,%U(EeSJE!"+)88!)#"X!%SK4`!N3QF[,!"+3QG1V3(
k2"mJE!"+RUJ!+%T'C`*J,L"X!%SJ+!!S8i$DJ#"&$"!!$@BB5SGQ#"em!!(rc'!
-8S8J44#m!!T6Ke+&B+)J2!!!!J#3!)FZ!'!Z5Q`!%'BN3UF[#dkkm#BYArrd3QG
)E!"52b`!8#mZrr41V3%#2"pq%'!%3SGmf8T'9X!-4[rC9X(!!@F83UG)E!"56Ud
#BMm'6Ud!dRcC3SFJ"p#X!#JT3!!S5Q`!%'B'1A`!"J!F$'`!!J!FCJC1V3*UB2)
-E!!%!"aQ!!#D5Q`!)Qp#3QF[,!!',blrkMmX!!j1V31D29rrj%*R,b`!'%kY!TS
GArr0,b`!1N(k!Di[##m-,b`!'%kY!V)jI!!#!"aJSQ"33UG)HJ'!6Ud#BNkY!E)
[,!!',b`!#N*R3UG)HJ&)6Ud#BNkkl-!jI!!$!"a#CbmX!"j1V3!k3UF[$%kk#CJ
YArrJ,`Y1V30b8fhkEQ!!!3J-E!!'!"aQ!!$+-#`!%&*!18!!%"!%C`!!JJaX!!8
!*&I!5L`!6PE"`!&R2%*R,`a#CdkkkC33(`S!!!&R)Mmm!!%[,!!H6Ud!1N+R,`a
1ZJNd,9rri#m,6Ud$FQ!!!+K#"'!!rBCJ-N*R,b`!'%kY!TSGArr02c`!!5mX!"j
1V3!k3UF[$%kk#2`YArrJ,`Y1V30b8fhkEQ"X,@lrk[rZ+8X!#Le,rqSQE[rZ3QF
[$$m(6VS*Y$eIrq3-K`!!!J"G`!a'rpPA`B!"C`*i!@!!r4T#CbmX!"K1V3+D(9r
rc8*R,b`!(NkY!$T#Tbm-6VS)P#eIrq![#dkY!h*6EITZB!4J!2cQ6Ud#FNcI'2"
1ALkI6R8I8Q9dFRNJE'PYDA3JCAKMC@9NC@3X)'GTGQPZCb"eF!484P43!!YQD@a
X)("KBfYPG%j@!!![$#KZ!!J`,!!b8N!j3!!b-#`!)P0!18!!)M!X!$453$P!!$3
`,!!q8N!j3!!q1A`!"!!F,b`!&%kY!S)SAdjH,Tp1G8j@rra)j`%)+'i!#%+RUA8
Z(jkX!%B-E!!"!%"Q"MPm!!`!3JaX!!%!2QB@)#`!0Y#(,`"`!Lm!6Ud#ZLPI!$C
J1!aX!!%!3&l!$'`!!3"#AX(!!@F+-#`!3P0!18!!3LmX!$B`,!"#5-![!%kY!VS
J(p#X!$BT3!!f,b`!0R!$,`"1V3,#+9m!1JbX!!!$B!!kE`JTI!!!!f!!1JbX!!!
#@!!kE!JTI!!!!PJ!1MPX!$i!3%cI%)"1ALkI6R919[rf51F('#KZ!!`U,J!)3UF
[,!!+6VVNFLCI$'`!#J!QCJBf[!!"B"J-E!!,!#CQ"MDm!!*J#Memrrm!%'!!!-3
["5!,9)![!%kkj1`J#e5!)%85%!*"!2p)`G+!8S%Z!3aX!!)!*&I!$'`!!`!N9m'
!!@F83UG)HJ#`6Ud#BLm(6VVNY(`&B&`-E!!%!#4Q&%+R5(S!MNkY!Q)["dkkj*K
m"@"!$'`!!3!NCK4#TdKk!'K1V3*L,`G1ZZ4mI!KJ*!aX!!8!*'B83UG)HJ"#6Ud
#BLm(6VVNB(`*B!JpI2rr!""J(N*R,``J44!3!N!!re4!8N$34P*!2`"1ZJ1Z29m
!%%cI'1"1AL"I8%p1d!PYB@0TER4[FfJ)EQ9dBA0MD@N!"@pMG'9d"@PYB@GP6PE
rf%MR$aJSEJ!1*Qi!#MSZ!!K1V3"L$%8!"'`J,b`!"Lm,3QG#TdKk![T1V3*L6VS
*$#m-6VVUU'!!!Ti-E!!%!"aQ#$Pm!!%!('!L$'`!!3!FCaS[,!!',`Y#Cd+R5(S
#SNkY!Q*1ZJM8B!!#E%+R,`Y1ZZ,N,9rrq&P&)'lrq$!S!!+`E!!3C`i`,!!`8N!
j3!!`B!!#3N*R,b`!'%kY!TSGArr[,`a1Z[eQ,``JE[ri2bJ!!Nkk!S")a5!&d+`
!+#P!!#K#Tbm,6VVUdLeIrr``,!!NDaL`I!!(EK*"qJ)L-J$Q584"!6!3r`Sm!!4
Q!!'L%#`!6NL!C`j63'F+8d"R!!$iB!!"9JaX!!%!*'FZ)'`!5L&Zrr`!)%M&)'`
!5L&&!#3JE!"+3QJ!,%*R,b`!5N*R6Ud#%M`IB!!")"!X!%4R#N)X!%4i!Ai"B!4
#4%*()'`!5N*S!#a#4VT(E`!!Q%M()!I3V[rm,8$rm#"Zrr!-%!!09X"R%,T(AX(
!!@F)8NG5V[r`B15k4fB+)!963$i!8klrm#!(N!"%8N")`#"X!%SK3!!N5-3J"0#
Zrr`JE!"+)8!!)%*R,b`!5N*R6Ud#%M`I5NCR!Q!X)!963,"(9m"Q&L"Zrr!-%!!
09m(!!@F)'A`!!3"%B(!J"e4!1!!q"'!!rfCJBN+R,`Y1ZZQF,9rrk%*R5'`!8Mm
X!&")E[rB6Ud"!M`I)'lrk#e3rpJJE[rS,@J!"2rF3UF[22rrr[mJE[rS-#J!#%M
!,`#S@#!I28$ri%*R5'`!8MmX!&")E[rB6Ud#+M`I5NCR-#mX!!B[#cmm!!0#TdK
k!)C1V3*L6VS'a%+R5'`!8NkY!Q)r"NkY!2)[$%kkk&"J4Q!H,b`!"Lm,3QG#TdK
k!%C1V3*L6VS'P#m-6VVS-'!Q$%8#!'B)1A`!"`!FB!BjI!!)!"``,!!38N!j3!!
3,b`!&%kY!S*-haM`6PiJAplm!!T1d!p*ER4PFQjKE#"&FR*[FLi*4'PcDb"'G@a
X!$BH8Q9MC@PfC@3JG@jPH("PBh4PC#"NBA4K)'*XEf0V!"9CEh8JD'&fC5"$8e)
JC'PcC@&cC5j19[rk51F"'#CZ!!T#TbmV!!T1ZZ!-+&mhI!!%!!ii[!!%1@i!#!!
#3QF[#cmm!!41ZJ!32Kp-haL!6PiJAea26Y"19[rm51F!'#KZ!!T#TbmX!!T1ZYr
+*Pm-8`!"9X!-8`!#9X(!!@F)1A`!#J!LB!BjI!!%!#)fNcPZ!!J!$M!X!#j53$P
!!#j#CbmX!!B[,!!+2bi!#%kY!jT+Afi12Acrr`!11A`!"3!FB#B[,!!k3IVjh#m
),``[,!!B6Ud#XN+RUA8TA`"'1A`!!3!q3Qi!$NcI'!"1AL"IA%p1d%j@rrT)j`%
)3UF[2!!!!9*1V3'U+&mJ$'BJ3UG)HJ'N6Ud#BN+R5(S"Y%kY!Q*1V3"53Ui!$'!
!!A"#V!!'3Q`!,%*X!#j#E!!`3Q`!-N*X!$3jEJ!+!#BjEJ!)!#4#V!!S1A`!$!"
#F#3T3!!f3Q`!8%)X!%3[,!!fF!-[!%kY!X)TA`!k$+`!!!0J!$T[##Pm!!!$B!!
k$+`!!!*B!$TX##Pm!!!#@!!k3Q`!2MPm!!%!3$Pm!!3!)N+R6Ud#NLPI!"K+V!!
BCLC#TdKk!241V3*L3UG)HJ$q6Ud#BNkY!&)[$%kY!**#VJ!-B!!!ZN+R2c`#!%*
R6Ud$8LPI!!T+V!!+CM"#TdKk!,K1V3*L3UG)HJ#d6Ud#BNkY!&*#CbmX!"K1V3+
L(Km[$%kY!**#VJ!-B(*#Th"3,`"1V3'U+9m!5NUX!%TQ1%+R5(S!G%kY!Q*#TdK
k!&T1V3*L6Ud!8N*R,b`!'%kY!U)H(bmX!!T1V30b,`a1V3#53Ui!$'!Q)'`!5M&
mrrm!'#PYqN!!&%+83L`!"#m-3Hhk8Lm)6Ud!QLe-!!a-ha#!6PiZRdje$h"KFQ&
YCA4PFL"LE'pMD`484P43!!e[GA4`GA3JF'&MDf9d"A4TE@9b%'0[EQjPBh4TEfi
JBQa[BfX!6PErq%MR"`JSEJ!)6Ud#LJaX!!-!*'FZ3QF[,!"+3QG1V3(#2KmJE!"
+3UJ!%L"X!%SaE!"3!"C#CbmX!%T#CdkY!GSq(`aX!!S!*PI!-L`!*'XBXR`!"fi
53IS!L$3"jNT%3J-`)2m+2!!%9m(!!@F53UG)E!"56Ud#BMmX!&"1ZZ(!,b`!"Nk
Y!f*#CbmX!"K1V3+D("p#CbmX!"K1V3+L("m[,!"+6Ud!NLmX!!T1V30b1A`!!`!
F+L`!+%*R,`a"lIT5,`K1V3#+2Km[$%kY!*)Y43!-60m3i%jH,Tp1G3!86PErr%M
R!"JQEJ!+3UF[+`!+6VVF@LKI1,`!!cPV!"!!!N*R,`X`,J!)@%!r!%kkr'!pA`!
160mB!%jH)&pF6dl36PErqNMR!4JSEJ!13UF[,J!+6VVF'#CI-#X!!V"X!""R$$!
X!$"53$P!!$"J)#m-6VVfXN*R,b`!'%kY!TSH(cPm!!B!(#mX!"41V3+#60mBJ%j
H)&rHr!!+6Y"19[ri51F('#KZ!!JQEJ!51Li!%%kY!')`,!!X8N!j3!!X3UF[#dk
kfkJZ(b"(2"!J4c#'-!CA3'F-8d"R*&0!CcTJ!!#'3QF[$#m,6VS!UK!IC`S[$#m
,2`91Z[JDB!!!JN*R,``[#dkk!)i3(fF+,``[#cm&6VVr,'"Q5Q`!%PI!)'`!"NM
RJ)"#TdMRJ)"#Tbm,6Ud"5L)I60m"!5m"6Ud$HL)I60m"!5*"-LJ!"V*49m'!!@F
3,``[#cm&6VS"9Lm-6VVLN!"J'#mX!!B[#cmm!!4#TdKk!"j1V3*L6VS!e#m,6Ud
$FNcI'1"1AL"Ih[`!$Nl3!5"19[rd51F$'#KZ!!`X,J!)3UF["NkkfX)QAcGV!!)
!!N+R3UF["NkY!8T1V30k)&mq%%TX!"*Q1$!X!"#`D`!#CK!jI!!"!")JE!!'-8F
!"Q"',b`!"Lm'3QG#TdKk!%j1V3*L6VS!9N)Z!""J-'!S)'`!"VjS!!CR(LmX!!B
["Mmm!!9#TdKk!#*1V3*L6VS!,%)Z!""J"Kem!!%!%%cI'-"1AL"I8%p1d!%J$Qp
XC#"MEfjZC@0dD@pZ!%j@rrC)j`-B+'i!%LCZ!!iq,!!',#`!#%+R3UF[#dkY!8T
1V30k)&mj8!!'3UF[#dkY!8SJAbPS!!`!##m-,`Xr,J!-,bi!#%kkhj!!18F!"LP
'!!K-haM!6PiJAplm!!j1d%j@r[K)j`%)3UF[,J!+6VVCULKI)!aBJ#m!3Hlqr#m
)6VVCr"!Zr[`#3!$r28$qq(i"B$K"l[lm%$"`!!*!!2m-3!!09m""l[lm%M"`!!*
"!2m-33!+9m'!!@F-)!G"l[lm%E`!)!!!8NGT"VjZr[K[`N+R5(S!+%kY!Q*"l[l
m,`K1V3#U)'i!$M&m!!8!(%cI%)"1AL"Ih[`!#Nl3(e4'9&!k)%9bFQpb)'CbEfd
JCQpbC@PREL"SEh0d1L!!!!AJ![J!"8j@!!"1ANje6PErq%MR!`JSEJ!)%"3#3!$
rDaL`I!!2EK*"qJ%L-J$Q584"!6!3r`Sm!!4R#%+Z!!aJ!!$S3SF3&!*!!2mp32r
iI!*J!!$+%$4J!!*!!2pV',"m!$pZ%N(k!1)b!1C*4%%"-"$r#M`!"'BD)!ITJ")
dB!!#33$rNR`!-%M"dS!Z!@!!!)J30'!!!N!!rfXBX(`!Efi53IS!R$)!jNP%33%
`%2m+2!!%CK`J"qQ!%M4J!!*"!2q5I!"KdR`!#NM"dS!Z!@"'%$4J!!*!!2pV',"
m!%pZ%N(k!&Jb!1C*4%%"-"$r#M`!"'BF)!ITJ")dB!!#33$rNR`!3G*m!!T)`G+
!,J&J"L!(kB!Z!&*'D3LmE[riE`$r-Le(!!a-ha$!6PiZRdje!(i!N!`$r`#3"J2
m6PErlNMR$`JSEJ!)F!%G[2q!!2&`!Kfm!!)!mA!$3MB!mA!%3MB!mAi"I!&J!!#
'3N83&!*!!2q`4ec!%M4`!!*"!2pV',*m!$pZ%N(k!-)d!HC+4%)$-#$r#M`!"&I
"`!&R*#!&jd!50(!!!N%!rp*!NR`!-$S"$%8!rfm'3Ui!$'"k8NGJUL!'(B8!qcJ
'%"3#3!$rX%GY)K!dF!!#3!$r$%!!,'B%8NGJ"N+Z!!aJ6&*'$%B!"'m!rhB3&!*
!!2q`4fd'3Ui!$'!b2A`!"2rkB"``,[rk%MC!q`*"!2mGJ3$a8d3-4!!"E3a6E[r
k$'i!!IrkE0`YE[rb!!a-ha$`6PiZRdje!2m!N!C19[rZ51F2##KZ!!K`!4fmri!
!mA!#(E`!!J$aF!0#0J$aF!4#0J$aIJ&m!@!!!)K#44!8!N!!rl"(A-!50(!!!N%
!rfXBXR`!2fi53IS!a$3"jNT%3J-`)2m+2!!%9m(!!@FQ)!A"r!!+%M4`!!*"!2r
53**m!$!k!3a&!2p["N+Z!!aJHP*(B+JJ"Kf&!2Xi"K!8!N!!rl"(E5)30(!!!N!
!r`a!!#jQ"&*(B!C#VJ!-B%a54Ja'!!4[!2pd%"3#3!$rX%GY"N+Z!!aJ-Mem!!6
rqQ!F-#lrqK)f32X#33$r(B%!m90%$%3!!@d-8flrqJaZ!!(rqQcF,@lrmJ!-60m
3m%jH,Tp1G32r!*!'6PB!!#mYp$j1V3+#6PiZRdje6PErl%MR$aJJEJ!),`K)HJ(
16Ud#bK!IC`SYEINB!!aJ!!'F)'i!#"!3!N!!rc`!3UFJ"PC!2`"#CdkY!e)QAb!
,CL"#TdKk!BC1V3*L3UG)HJ'%6Ud#BNkY!&*#VJ!-B!!"A%+R3UG#Tbm,6Ud"5Nk
Y!hT1V30U+&mB[!!"8S`BKP+-28Erl(i"B"3JEJ!)%$"`!!*!!2mBJ&+-8NGT"Vj
Zrqa[jN)8+fhk323q3Uhd3N*Yp&T#4f"X$%F!!@B#B'SJ"d(Yp%EP3%MRJ)"#Tb)
(3qhi"Z9",c%3!$mm!#T)ji#!3QG1V31+-Kp-h`%"2`&$qJ$Z,`P#TdkY!i)L(dc
I!3%KJ3!!3QFJ"d(Yp%EP3#m`!!![#b!'9N!r!%kY!jSi(e*($%F!!@q1,`Y1V30
b3UG1V3+5+Kp+K@BH3UG)HJ#-6Ud#BN+R5(S!I%kY!Q*1V3"53Ui!$'"LF!N[!%(
krS`[#%+R,`91V3+U6Ud#DN*R,`91V3+D%"p)J$J!3QF["8kY!U)3(dL!1!"#4f!
5)!G"lI4'j8![-!!!6Ud$BP*($%F!!@rS5Qhd@QB)F!%Y3!!-B!BYEI4#!!a-haM
`6PiZRdje"A4TE@9b"e9%8%j"688'F'&MDf9d!!*YC3"19[ri51F!'&*Yp&T#Td+
R3UF[,J!56Ud"5NkY!hT1V30U+&m3,!!"!N!!rdM!d)a8J#K!%"3#3!$r$%!!!PI
!5Uhd3PI"`!&R%N+R6VVp[L!-9)!Q3#Y6p%*J$JaY!!(d@QB'3UG1Z[fN,bi!%Nk
Y!h*-haJ!6PiJAplm!!j1d!#3!a!$)!!"6PB!!%kY!aT1ANje!!!(i!-S!!&19[r
m,`HT-$em!5$rr(!"(E`!&!$m3UFr2!!"5'lrr+Na+errm#mYrr![1J"5U8e#Tcm
m!3#T[bYIrr4#Tcmm!3'T[bYIrrK#Tcmm!3+T[bYIrraq!@!1)!IP3#me!1a#CkN
e8NF-4`!%EqbT0bmYrr3r2!!%U6SZ(djH6R9%8PC56PB!!#"m!!!*##e3!!K1ANj
e6PB!!$Ym%!$kSMYm%!$i#N+R6VVrfM!Yq!V3EIULd(`)!0"m#!$3I"J!d(`)!%M
!)Kq5J#m"6Ud#)NkY!AT1V3'+6Ud"LNkY!BT1V3'+6Ud"LN(Yp5B[#+KZU2ir22r
r3QFJ(k!bU4*1Z[m#3UHTHkK3UFa"lIr)3qhdXL$C)0P)EIr!2c`!"$mm!"J`,Ir
1@8!r!$!YrmaC3$m!U+G1ZJ$'3QG1V3'55PpR%%+R5(S!PNkY!Q*#CdkY!,T#Tcm
YqU*1ZJ$'+erk4%kk!5j1ZJ216VS#qNkk!hj1ZJB%6VS'8"Ym!!(q["Ym!!(q[4Y
m!!(q[N)YrVp#,IUm3Lhl[%)YrEa"lIbm3qhi'("!)0P63'lk1h`!!IUb,bhrq$m
m!!%I2!!"U89#EIUi'h`!!IUa3UhkV%+YqUK#,IUP3Lhrl8jH6R8I3f&Z*h3JEh"
PEL"0B@-J8(*[G'pMEf`J8'&MDf&RC8j@!!"#Tcmm!$&#Td+RUA`VArBd3Qhe+N+
Yp5a#VI8`6Pj1G8j@rrT)j`%)3Qhk2N)Yq6e#VIN`-#i!#0"m!"!q!#!(!N!!!@F
#8NFr"dkY!PT"lINJ+%JV62T!3L`!$NkY!Z)[$%Kk!#C1V3,U18F!#%(Yq5!T5!!
++8`!"#e-!!T-ha#!6PiJAe426Y!%6@&TEJ"19J!!3LhkRd*YqT4#VIU@3UhkQN+
YqT!!6Pj1G8j@rr4)j`%)+'i!#%*R5'lrp%KZrrC1V3%+5PpR"%*Zrr3`,[rd5-$
3[)!#!!!SJ#PmJ!)!3!!%+Ab!!J"!!!JTI)!#!%!!$%(X!""$qJ#`)0NJf6#43H`
"%N2k!*SJf5$C'A`!!3%33QG)HJ"d3QG)E[rm6Ud!iMiI5NGQ@#em!!!#%[ri3QF
r,[rm2c`!!8+R6Ud#-MiI5NGQ%N*R2blrr%KZrrJ[$%kY!1Sq(d*R2blrr%kY!0S
q(a!X!4"R&N+R,a3[22q3!`#S@$!Zrr4)`0#I+)"-ha#!6PiZRdje&%0eFh4[E@P
kBA4TEfiJ9Q&XG@9c!!G9EQYZEhGZ#%&`F'aP68&$!%j@rra#EIC%3Qhf3N*YpN"
#EIBq3Qhf2%*YpMT#EIBi3QG)E[rm5'lrrNkY!3T+AfF#B'iEE[rppX!lE[rqpVj
`5"Y!pX&#EIDk-$`!raY!pVa`5"Y!pVdEI2rrpNG#,IC'3Uhf`N+RF"J[!%kY!DS
VArDf3UG1V3$k+erfaN+R,bhk3%(Y!#S[#$mm#!")HJ!@3UG1V3*k+erfbLeYpXS
!#%jH6R8)39*3)&*PBhB!6PErrLm(3NGJ'L!(3HhfjZG!3V!!!#!(3HhfjZG!3V!
!"&*($%F!$frJ3Qhfj$Ym!!2fhN+YpYSlI!!"pY*#VIE@3Qhfe%+R2c`!!8(Y!4S
[#%kY!9SVArEJ5Uhfi'BB3UG)HJ!H6Ud#BN+R5(S!%%kY!Q*1V3"D,Kp1ANje!NP
3!!4*3de3!%j@!!"#Tcmm!!0"l3%5,`K1V3&D+erfcNUYpXjQ'%+R5(S!)NkY!Q*
#TdKk!!j1V3*L6Ud!@NjH6R8)5@jdCA*1CA3!!dG(8%j@rGK)j`m)3Hlpk#m)6VV
pD#YZrHMj'#YZrHcj(#YZrI$i"LYZrI6i!N(YprK$l[hi)0NJf6#4'flqq2JA3Hh
i'%2Zr[T`3#$C8d"ZqN*Ypk*#EII11h`!!II-3Qhhj%*Ypq*#EIIJ3QhhhN*Yppa
#EIID3Qhhf%*YppC#EII83QhhdN*Ypp!S2!!!J!"#Tbki!USJAbm)3UFZZ!+U)"m
JAb)3NS![!5m%6Ud#ZLSIA)8m2!*B3Qhi$%+Yq!j#VIJ53QhhlN+Ypr"#VIId)!9
6J$e!rGK#4f"J3UG`%#m!6Ud"ULKI)!aQ%%+R5(S"0%kY!Q*#CdkY!,T#,!!%3U`
!#N*X!!j#TdM',`C1V3'U+9m!"NUX!!CQ%%+R5(S!fNkY!Q*#CdkY!,S[$%(Ypqi
[#%kY!*T54fN'[Qlpf'qD3UG`'Lm!6Ud"SLYIpqSJEIIU)&"#D!!#3UF[,IT!3Hd
"+Lm)2bhi#NKk!)C#TdkY!RSVArIQ,bhhjN+R6VVp!N(Yq!`[#%(Ypqi[#%kY!A)
pI!")rHC#CdKZrHC"l3&U,`K1V3"k5PpR'%+R5(S!0%kY!Q*#TdKk!"*1V3*L6Ud
!@NcI%2"1ANje&R0[BfYPG#"QEh)J3A"`E'9LGA-J59!!$84%8%p`C@j6Ef0VCA3
(59"%C@eeH#K5B@iJEh9d)'pQ)("KBfYPG#"cG'pbB@GP)'4eFQPZCb"TEPpTEQP
d!"jTEPpTEQPd1L"MB@iRG#"YB@YP)'CbC@8JFA9PG@8!6PB!!%+YqNa#EIT33UF
r2!!43Hd$SLm)6Ud"@LYIqNK+VIT)CKK#TdKk!#*1V3*L3UG)HJ!16Ud#BNkY!&T
1ANje#%PZG'9b6Q9d!!094&"19J!!3QhkEN*YqP*#VIT83Uhk@%jH6R8!!!6d!c!
!#dj@!!"#TbmZ!!K1V3%k,9m!$%jH,Tp1G8j@!!!J,J!)8)!Y3!!-6PiZRdje6PE
rrLm(-#i!#P"!8N!q!#!(!N!!!@F#8dG#Tcm(2bi!#%kY!6)YA`!-,Kp1ALkI6R9
19J!!,bi!#%kY!8*1ALkI6R919[rf51F!'#KYqN`J$'F`-#`!","Z!""A`$)X!!D
bEJ!59m(!!5)X!!LbVJ!89m(!!@F'3Ui!''"Q*N`S9'$-3UG`&#m!6Ud"ULKI)!a
Q(N+R5(S!CNkY!Q*#TdKk!&"1V3*L6Ud!8N+Z!"KJ-NUYqNaR"#D-B!3V62T-3T3
jEJ!3!!3jEJ!5!!BTEJ!8!!JTEJ!-!!`TEJ!)!"!Y6!!B60mB!%jH)&rHr!!36Y!
+BfpZEQ9MG'P[EJ!$98436PErr#m-+'hk6#!-Cb!`,!!'X'i!#&I!)L`!#,+Z!!T
A`F!"C`4J"Q!#+&4Jh#e-!!iSAdjH)&pF6dl36PErr%MR!"JQEJ!))!YQ!Q!`Yqh
k6'B+)'hk6#Y3qNaJ'LKYqN`J$'F+Yp4Q!Q!%+&4JmL!-CJ*J##L6,`Y1V3#560m
B!%jH,Tp1G8j@!!"+EIT3C``pEIT3!!K5EIT3B#4#Cd+RUA@SDcYIqP!-E32SqP"
X"JCY!qMk8$eYqP!!#&*YqP"1ANje6PErf%MR$aJX,J!16Ud!BN+R,`C1V3&++Kp
#Tbm&6VVpl#KI1#`!",KZ!!a[#Lm'6Ud"3Q!!!2`q,!!'5NGR!!#3!"!Z!!d#3!!
"Ca)`,J!-5-$3M#e!rpSJE[rD3K!YEJ!)rq`J45eS!"$rm(!!(8$rp(!4(8$rp6e
X!!6rpN*R3Hlrl#m)F!a)`)(m!!)r!%kY!'SjA`!'3UG#Cbm--#i!$&*!5-#"r!!
#2`"1V3"U-"p)`#m!U&SJ(ce!rqBj4`!'[QlrjQF),`C1V3&#B')QEIT-)!YR1NT
V!!4@`$)V!!5bE!!#9X(!!@F%*P0J)%UV!!aR'#m')!443$m!,bi!##mV!"![+`!
-6Ud!5Q!LB-*#CbmZ!!K#Tbm'6Ud"5Mmm!!01V3%L29rrf#m'6VVp1NcI'2"1AL"
Ih[`!#Nl36PErjNMR"aJU,J!13UG#TbmZ!!T1V3&+6VVmXLKI-#i!#&"!2J!J"`*
!!!&R#NM()!c3KbC!3K-J45`S!!Jj4`!%)%8iU!!%)%8jD!!'!!*#Tbm'6Ud"8Le
Irr!Y4[rdF!!G32riF"%G32rj2@`!"2rk3QG"l[r`,`K`$%M!JI`!!Mm!6Ud!DMP
I!!C#Td*R,``J"e*!5-#"r!!#2`"1V3"U-"p)`#m!U&SJ(cP!!!C#CbmYqNJ[,J!
+2`F["NkY!@)pA`!560mBi%jH)&rHr!!+6Y"19J!!,``SEJ!),bi!$%KXr[a1V3'
k,bcqpMmXr[T)E2ld5'cqm%KXrZLTM5mXr[")E2lmUBmSAdjH)&p36dl36PEqk#m
-3UFr2!"G3UG`rbm!UA`YArlf+'hk6$em!!EqqL!-Ch3`,!!%5-![!#m16VVrMP*
Zr[S`,!!'5-![!#m16VVrI&*Zr[S[,!!)5'lqr%kY!()[,[lf2blqqNKZr[4)E[l
`5'lqk+Q0,blqm%KZr[bTMe*Zr[S[,!!-,`j1Z[mq8QlqqLmX!"![$Nkkrc"5E[l
k+&4JL#mZr[C1V3'D+&p1ANje!*$r!*"j(!)5!!P84P43!*!$8NC548B!N!0H3Nj
%6!#3!fT"6&*8!!%!GN4*9%`!#3#14%a24`!(!3C048j9!!)"CNP$6L-!!!'+8dp
'9!!!!CC$6d4&!!B"SJ!!rrm!N!Q!rrm!N!-S!*!&J2rr!*!$-`#3"!&0rrm!N!0
6!*!%!TVrr`#3!f-!N!84rrm!N!0c!*!&02rr!!!"C`#3"62rr`!!!HN!N!8prrm
!!!)e!*!%m&rrr`!!!UN!N!8qrrm!!!1R!*!%!Ecrr`!!"%F!N!3$#Irr!!!%U`#
3"8lrr`!!"@-!N!9Hrrm!!!Gj!*!&#rrr!!!)E3#3"6,rr`!!#)B!N!8arrm!!!L
I!*!&22rr!!!)`J#3"2"Irrm!!!MY!*!&(rrr!!!*"J#3"8hrr`!!#6%!N!9Grrm
!!!PB!*!%!3$rr`!!#Am!N!3"!Irr!!!*cJ#3"!%#rrm!!!T9!*!&J2rr!!!+``#
3"3%!!"!!#mF!N!Errb!!,S%!N!8"rrmd!$)G!*!&![rr-!"JU`#3"32rrc!!NXd
!N!8%rrm`!*La!*!&"Irr-!#Ba3#3"3Errc!!S+N!N!3+8dC04f9d4QPXC3#3r`#
3j#k'!:
!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu       | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.