PROGRAM modem;
     {Written by Jack M. Wierda  Chicago Illinois
     This program is in the public domain.

     LANGUAGE: UCSD Pascal
     FILES:    MODEM3.PAS -- main program
               MDM3-Z80IO.Z80 -- serial line interface for Z80
               MDM3-8080IO.Z80 -- serial line interface for Intel 8080

     This program is basically a re-write in PASCAL of Ward Christensen's
Modem Program which was distributed in CP/M User's Group Volume 25. Identical
and compatible options are provided to allow this program to work directly
with Ward's program running under CP/M. One difference is that when sending
files the PASCAL or CP/M transfer mode must be selected. The PASCAL mode
transfers files between two systems running PASCAL, while the CP/M mode is
used when the receiving system is running CP/M. Basically the CP/M mode
provides the linefeeds required to make a PASCAL file compatible with CP/M.
When CP/M files are received they contain linefeeds, these can be deleted
using the editor to make the file compatible with PASCAL. CP/M files may also
contain tabs which the PASCAL editor does not expand.
     External assembly language routines are used to read the status, and read
or write the keyboard and modem ports. These routines are available as
separate files for the 8080 and Z80 processors. The port and flag definitions,
and the timing constant for the one second delay should be changed as required
for your particular hardware.
     The program has been tested with text files only, and may not work
correctly for code or other types of files.
     The PDP-10 mode transfers PASCAL files to a DEC SYSTEM-10 computer.}

CONST
     nul = 0;
     soh = 1;
     ctrlc = 3;
     eot = 4;
     errormax = 5;
     retrymax = 5;
     ctrle = 5;
     ack = 6;
     tab = 9;
     lf = 10;
     cr = 13;
     ctrlq = 17;
     ctrls = 19;
     nak = 21;
     ctrlz = 26;
     space = 32;
     delete = 127;
     lastbyte = 127;
     timeout = 256;
     loopspersec = 1800       {1800 LOOPS PER SECOND AT 4MHZ};
     kbsp = 0           {KEYBOARD STATUS PORT};
     kbdrf = 128        {KEYBOARD DATA READY FLAG};
     kbdp = 1           {KEYBOARD DATA PORT};
     kbmask = 127       {KEYBOARD DATA MASK};
     dchdp = 128        {D. C. HAYES DATA PORT};
     dchmask = 255      {D. C. HAYES DATA MASK};
     dchsp = 129        {D. C. HAYES STATUS PORT};
     {STATUS PORT BIT ASSIGNMENTS}
     rrf     =    1   {RECEIVE REGISTER FULL};
     tre     =    2   {TRANSMIT REGISTER EMPTY};
     perr    =    4   {PARITY ERROR};
     ferr    =    8   {FRAMING ERROR};
     oerr    =    16  {OVERFLOW ERROR};
     cd      =    64  {CARRIER DETECT};
     nri     =    128 {NO RINGING INDICATOR};
     dchcp1 = 129       {D. C. HAYES CONTROL PORT 1};
     {CONTROL PORT 1 BIT ASSIGNMENTS}
     epe     =    1   {EVEN PARITY ENABLE};
     ls1     =    2   {LENGTH SELECT 1};
     ls2     =    4   {LENGTH SELECT 2};
     sbs     =    8   {STOP BIT SELECT};
     pi      =    16  {PARITY INHIBIT};
     dchcp2 = 130       {D. C. HAYES CONTROL PORT 2};
     {CONTROL PORT 2 BIT ASSIGNMENTS}
     brs     =    1   {BIT RATE SELECT};
     txe     =    2   {TRANSMIT ENABLE};
     ms      =    4   {MODE SELECT};
     es      =    8   {ECHO SUPPRESS};
     st      =    16  {SELF TEST};
     rid     =    32  {RING INDICATOR DISABLE};
     oh      =    128 {OFF HOOK};

VAR file1 : text;
   option, hangup, return, mode, baudrate, display, filemode : char;
   sector : ARRAY[0..lastbyte] OF integer;
   dchcw2 : integer;
   ovrn1, ovrn2, showrecv, showtrans : boolean;

FUNCTION stat(port,exr,mask:integer): boolean;
external;

FUNCTION input(port,mask:integer): integer;
external;

PROCEDURE output(port,data:integer);
external;

PROCEDURE sendline(sldata:integer);
BEGIN
 REPEAT
 UNTIL stat(dchsp,tre,tre);
 output(dchdp,sldata);
 IF showtrans
 THEN
   IF (sldata = cr) OR ((sldata >= space) AND (sldata <= delete))
   THEN
     write(chr(sldata))
END;

FUNCTION readline(seconds:integer): integer;

VAR j : integer;
BEGIN
 j := loopspersec * seconds;
 REPEAT
   j := j-1
 UNTIL (stat(dchsp,rrf,rrf)) OR (j = 0);
 IF j = 0
 THEN
   readline := timeout
 ELSE
   BEGIN
     j := input(dchdp,dchmask);
     IF showrecv
     THEN
       IF (j = cr) OR ((j >= space) AND (j <= delete))
       THEN
         write(chr(j));
     readline := j
   END
END;

PROCEDURE sendstr(str:string);

VAR j: integer;
BEGIN
 FOR j := 1 TO length(str) DO
   sendline(ord(str[j]))
END;

FUNCTION uppercase(ch : char) : char;
BEGIN
 IF ch IN ['a'..'z']
 THEN
   uppercase := chr(ord(ch)-space)
 ELSE
   uppercase := ch
END;

PROCEDURE purgeline;

VAR j : integer;
BEGIN
 REPEAT
   j := input(dchdp,dchmask)      {PURGE THE RECEIVE REGISTER};
 UNTIL NOT stat(dchsp,rrf,rrf)
END;

PROCEDURE dchinitialize;
BEGIN
 writeln('Waiting for carrier');
 REPEAT
   BEGIN
     IF option IN ['R','S']
     THEN
       BEGIN
         output(dchcp1,pi+ls2+ls1);
         output(dchcp2,oh+rid+txe+dchcw2)
       END;
     IF option IN ['C','P','T']
     THEN
       BEGIN
         output(dchcp1,ls2+epe);
         output(dchcp2,oh+rid+txe+dchcw2)
       END
   END
 UNTIL (stat(dchsp,cd,cd)) OR (input(kbdp,kbmask) = ctrle);
 purgeline;
 writeln('Carrier detected')
END;

PROCEDURE makesector;

VAR j : integer;
   ch : char;
BEGIN
 j := 0;
 IF ovrn1
 THEN
   BEGIN
     sector[j] := cr;
     j := j+1
   END;
 IF ovrn2
 THEN
   BEGIN
     sector[j] := lf;
     j := j+1
   END;
 ovrn1 := false;
 ovrn2 := false;
 WHILE (NOT eof(file1)) AND (j <= lastbyte) DO
   BEGIN
     WHILE (NOT eoln(file1)) AND (j <= lastbyte) DO
       BEGIN
         read(file1,ch);
         IF ord(ch) <> lf
         THEN
           BEGIN
             sector[j] := ord(ch);
             j := j+1
           END
       END;
     IF eoln(file1)
     THEN
       BEGIN
         readln(file1);
         IF filemode IN ['P']
         THEN
           IF j <= lastbyte
           THEN
             BEGIN
               sector[j] := cr;
               j := j+1
             END
           ELSE
             ovrn1 := true
         ELSE
           BEGIN
             IF j <= (lastbyte-1)
             THEN
               BEGIN
                 sector[j] := cr;
                 sector[j+1] := lf;
                 j := j+2
               END
             ELSE
               IF j = lastbyte
               THEN
                 BEGIN
                   sector[j] := cr;
                   j := j+1;
                   ovrn1 := true
                 END
               ELSE
                 IF j > lastbyte
                 THEN
                   BEGIN
                     ovrn1 := true;
                     ovrn2 := true
                   END
           END
       END
   END;
 CASE filemode OF
   'P' : IF j <= lastbyte
         THEN
           FOR j := j TO lastbyte DO
             sector[j] := space;
   'C' : IF j <= lastbyte
         THEN
           FOR j := j TO lastbyte DO
             sector[j] := ctrlz
 END
END;

PROCEDURE termcomp;

VAR kbdata, dchdata : integer;
   crflag : boolean;
BEGIN
 crflag := false;
 dchinitialize;
 WHILE stat(dchsp,cd,cd) AND (kbdata <> ctrle) DO
   BEGIN
     IF stat(kbsp,kbdrf,kbdrf)
     THEN
       BEGIN
         kbdata := input(kbdp,kbmask);
         IF option IN ['C']
         THEN
           write(chr(kbdata));
         output(dchdp,kbdata)
       END;
     IF stat(dchsp,rrf,rrf)
     THEN
       BEGIN
         dchdata := input(dchdp,dchmask);
         IF option IN ['C']
         THEN
           output(dchdp,dchdata);
         IF dchdata = cr
         THEN
           crflag := true;
         IF (dchdata = lf) AND crflag
         THEN
           crflag := false
         ELSE
           write(chr(dchdata))
       END
   END
END;

PROCEDURE pdp10;

VAR wait10 : boolean;
   dchdata : integer;
   ch : char;
   filename, pdp10file : string;
BEGIN
 showrecv := false;
 showtrans := true;
 wait10 := false;
 write('Filename.Ext ? ');
 readln(filename);
 reset(file1,filename);
 IF option IN ['P']
 THEN
   BEGIN
     write('PDP-10 Filename.Ext ? ');
     readln(pdp10file);
     dchinitialize;
     sendline(cr);
     sendstr('R PIP');
     sendline(cr);
     REPEAT
     UNTIL readline(5) IN [ord('*'),timeout];
     sendstr(pdp10file);
     sendstr('=TTY:');
     sendline(cr)
   END
 ELSE
   BEGIN
     write('UNIX Filename.Ext ? ');
     readln(pdp10file);
     dchinitialize;
     sendline(cr);
     sendstr('cat > ');
     sendstr(pdp10file);
     sendline(cr)
   END;
 WHILE (NOT eof(file1)) AND (stat(dchsp,cd,cd)) DO
   BEGIN
     WHILE NOT eoln(file1) DO
       BEGIN
         IF NOT wait10
         THEN
           BEGIN
             read(file1,ch);
             sendline(ord(ch))
           END;
         IF stat(dchsp,rrf,rrf)
         THEN
           BEGIN
             dchdata := input(dchdp,dchmask);
             IF dchdata = ctrls
             THEN
               wait10 := true;
             IF dchdata = ctrlq
             THEN
               wait10 := false
           END
       END;
     readln(file1);
     sendline(cr)
   END;
 close(file1);
 REPEAT
 UNTIL readline(1)=timeout;
 IF option IN ['P']
 THEN
   BEGIN
     sendline(ctrlz);
     sendline(ctrlc);
   END
 ELSE
   BEGIN
     sendline(eot)
   END;
 termcomp
END;

PROCEDURE sendfile;

VAR j, k, sectornum, counter, checksum : integer;
   filename : string;
BEGIN
 write('Filename.Ext ? ');
 readln(filename);
 reset(file1,filename);
 sectornum := 1;
 dchinitialize;
 ovrn1 := false;
 ovrn2 := false;
 REPEAT
   counter := 0;
   makesector;
   REPEAT
     writeln;
     writeln('Sending sector ', sectornum);
     sendline(soh);
     sendline(sectornum);
     sendline(-sectornum-1);
     checksum := 0;
     FOR j := 0 TO lastbyte DO
       BEGIN
         sendline(sector[j]);
         checksum := (checksum + sector[j]) MOD 256
       END;
     sendline(checksum);
     purgeline;
     counter := counter + 1;
   UNTIL (readline(10) = ack) OR (counter = retrymax);
   sectornum := sectornum + 1
 UNTIL (eof(file1)) OR (counter = retrymax);
 IF counter = retrymax
 THEN
   BEGIN
     writeln;
     writeln('No ACK on sector')
   END
 ELSE
   BEGIN
     counter := 0;
     REPEAT
       sendline(eot);
       counter := counter + 1
     UNTIL (readline(10) = ack) OR (counter = retrymax);
     IF counter = retrymax
     THEN
       BEGIN
         writeln;
         writeln('No ACK on EOT')
       END
     ELSE
       BEGIN
         writeln;
         writeln('Transfer complete')
       END
   END;
 close(file1)
END;

PROCEDURE readfile;

VAR j, firstchar, sectornum,sectorcurrent, sectorcomp, errors,
   checksum : integer;
   errorflag : boolean;
   filename : string;
BEGIN
 write('Filename.Ext ? ');
 readln(filename);
 rewrite(file1,filename);
 sectornum := 0;
 errors := 0;
 dchinitialize;
 sendline(nak);
 sendline(nak);
 REPEAT
   errorflag := false;
     REPEAT
       firstchar := readline(20)
     UNTIL firstchar IN [soh,eot,timeout];
   IF firstchar = timeout
   THEN
     BEGIN
       writeln;
       writeln('SOH error');
     END;
   IF firstchar = soh
   THEN
     BEGIN
       sectorcurrent := readline(1);
       sectorcomp := readline(1);
       IF (sectorcurrent+sectorcomp)=255
       THEN
         BEGIN
           IF (sectorcurrent=sectornum+1)
           THEN
             BEGIN
               checksum := 0;
               FOR j := 0 TO lastbyte DO
                 BEGIN
                   sector[j] := readline(1);
                   checksum := (checksum+sector[j]) MOD 256
                 END;
               IF checksum=readline(1)
               THEN
                 BEGIN
                   FOR j := 0 TO lastbyte DO
                     write(file1,chr(sector[j]));
                   errors := 0;
                   sectornum := sectorcurrent;
                   IF display <> 'R'
                   THEN
                     BEGIN
                       writeln;
                       writeln('Received sector ',sectorcurrent)
                     END;
                   sendline(ack)
                 END
               ELSE
                 BEGIN
                   writeln;
                   writeln('Checksum error');
                   errorflag := true
                 END
             END
           ELSE
             IF (sectorcurrent=sectornum)
             THEN
               BEGIN
                 REPEAT
                 UNTIL readline(1)=timeout;
                 writeln;
                 writeln('Received duplicate sector ', sectorcurrent);
                 sendline(ack)
               END
             ELSE
               BEGIN
                 writeln;
                 writeln('Synchronization error');
                 errorflag := true
               END
         END
       ELSE
         BEGIN
           writeln;
           writeln('Sector number error');
           errorflag := true
         END
     END;
   IF (errorflag=true)
   THEN
     BEGIN
       errors := errors+1;
       REPEAT
       UNTIL readline(1)=timeout;
       sendline(nak)
     END;
 UNTIL (firstchar IN [eot,timeout]) OR (errors = errormax);
 IF (firstchar = eot) AND (errors < errormax)
 THEN
   BEGIN
     sendline(ack);
     close(file1,lock);
     writeln;
     writeln('Transfer complete')
   END
 ELSE
   BEGIN
     close(file1);
     writeln;
     writeln('Aborting')
   END
END;
BEGIN
 writeln('Modem, 7-July-79');
 REPEAT
   REPEAT
     write('Option : C(omputer), P(DP-10), R(eceive), S(end), T(erminal)');
     write(', U(nix) ? ');
     read(option);
     option := uppercase(option);
     writeln
   UNTIL option IN ['C','P','R','S','T','U'];
   REPEAT
     write('Mode : A(nswer), O(riginate) ? ');
     read(mode);
     mode := uppercase(mode);
     writeln
   UNTIL mode IN ['A','O'];
   IF mode IN ['O']
   THEN
     dchcw2 := ms
   ELSE
     dchcw2 := 0;
   REPEAT
     write('Baud rate : 1(00), 3(00) ? ');
     read(baudrate);
     writeln
   UNTIL baudrate IN ['1','3'];
   IF baudrate='3'
   THEN
     dchcw2 := dchcw2+brs;
   IF option IN ['R','S']
   THEN
     BEGIN
       REPEAT
         write('Display : N(o), R(eceived), T(ransmitted) data ? ');
         read(display);
         display := uppercase(display);
         writeln
       UNTIL display IN ['N','R','T'];
       IF option = 'S'
       THEN
         BEGIN
           REPEAT
             write('File mode : C(pm), P(ascal) ? ');
             read(filemode);
             filemode := uppercase(filemode);
             writeln
           UNTIL filemode IN ['C','P']
         END;
       CASE display OF
         'N': BEGIN
                showrecv := false;
                showtrans := false
              END;
         'R': BEGIN
                showrecv := true;
                showtrans := false
              END;
         'T': BEGIN
                showrecv := false;
                showtrans := true
              END
       END
     END;
   CASE option OF
     'C': termcomp;
     'P': pdp10;
     'R': readfile;
     'S': sendfile;
     'T': termcomp;
     'U': pdp10
   END;
   REPEAT
     writeln;
     write('Hangup : Y(es), N(o) ? ');
     read(hangup);
     hangup := uppercase(hangup);
     writeln
   UNTIL hangup IN ['Y','N'];
   IF hangup IN ['Y']
   THEN
     output(dchcp2,0);
   REPEAT
     writeln;
     write('Return to system : Y(es), N(o) ? ');
     read(return);
     return := uppercase(return);
     writeln
   UNTIL return IN ['Y','N'];
 UNTIL return IN ['Y']
END