{ File: [20,50]GENSRVCOD.PAS       Last Edit: 9-AUG-1989 09:37:30 
}


{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Generic server program procedures and main program code.

  History:

    03-Aug-89.  Philip Hannay.  Created from GATESRV.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE DO_WRITE_DETAIL;

  { Produce debug detail for a device send operation using contents
    of SEND_BUFFER and SEND_IOSB.   Called only if DEBUG_ON is true, so
    we use DEB device unconditionally. }

var
  i: integer;
  debug_buf: packed array [0..80] of char;
  hexword: word;
  hexasc: ch4;

begin
sassign(debug_buf,'device send');
case Send_IOSB.byt[1] of
  is_suc: sconcat(debug_buf,' successful');
  otherwise sconcat(debug_buf,' error');
  end; {case}
swrite(deb,debug_buf);
writeln(deb);
writeln(deb,'Send_IOSB.int[1]=',Send_IOSB.int[1]:-6,
    ' (octal) and Send_IOSB.int[2]=',Send_IOSB.int[2]:-6,' (octal)');
if (ord(send_buffer[0]) > 1)
  then begin
    writeln(deb,'Message text (in hex) sent was:');
    write(deb,'   ');
    for i:= 1 to ord(send_buffer[0]) do
      begin
      { convert character to a hex digit for debug purposes }
      hexword:= loophole(word,ord(send_buffer[i]));
      cwotas(hexword,hexasc,0,16);
      write(deb,hexasc[3],hexasc[4]);
      if (i mod 10 = 0) 
        then begin
          writeln(deb);
          write(deb,'   ');
          end
        else begin
          write(deb,',');
          end;
      end; {for}
      writeln(deb);
    end;
writeln(deb);
end;



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE DO_READ_DETAIL;

  { Produce debug detail for a device transaction in RAW_BUFFER
    and RAW_IOSB.  This routine is called only if DEBUG_ON is true,
    so we use debug device DEB unconditionally for output. }

var
  i: integer;
  debug_buf: packed array [0..80] of char;
  hexword: word;
  hexasc: ch4;

begin
sassign(debug_buf,'Device transaction received -');
case raw_iosb[cur_qio].byt[1] of
  is_suc: sconcat(debug_buf,' success');
  is_tmo: sconcat(debug_buf,' timed out');
  ie_cks: sconcat(debug_buf,' bad checksum');
  ie_ies: sconcat(debug_buf,' bad DLE sequence');
  ie_ver: sconcat(debug_buf, ' parity error');
  ie_dao: sconcat(debug_buf, ' data overrun error');
  ie_bcc: sconcat(debug_buf, ' framing error');
  otherwise sconcat(debug_buf,' other error');
  end; {case}
swrite(deb,debug_buf);
writeln(deb);
if raw_iosb[cur_qio].byt[1] in [is_suc,is_tmo]
  then begin
    writeln(deb,'Message text (in hex) received is:');
    write(deb,'   ');
    for i:= 1 to Raw_IOSB[cur_qio].int[2] do 
      begin
      { convert character to a hex digit for debug purposes }
      hexword:= loophole(word,raw_buffer[cur_qio,i]);
      cwotas(hexword,hexasc,0,16);
      write(deb,hexasc[3],hexasc[4]);
      if (i mod 10 = 0) 
        then begin
          writeln(deb);
          write(deb,'   ');
          end
        else begin
          write(deb,',');
          end;
      end; {for}
    writeln(deb);
    end;
writeln(deb);
end;



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE POST_DEVICE_READ;

  { Post a device read no wait QIO for CUR_QIO, using the QIO_done_flag,
    raw_buffer, and raw_IOSB associated with CUR_QIO.  If the read fails to
    post (directive error), it is fatal, and we will exit program ASAP. 
    After posting QIO, advance CUR_QIO to point to the next QIO in line
    that will be completed.  This routine is to ONLY routine that should
    alter the value of CUR_QIO after initialization or communication reset. }

var
  i: integer;

Begin
{ Issue a read (or read with prompt) nowait qio to the device port.
  The device specific ISSUE_READ_QIO will take care of the qio particulars
  including insuring that the qio will terminate properly. }
raw_param_list[1]:= sadr(raw_buffer[cur_qio]);
raw_param_list[2]:= Max_tran_size;
for i:= 3 to 6 do raw_param_list[i]:= 0;
issue_read_qio(raw_IOSB[cur_qio],raw_param_list);  
{ make sure read issued okay }
if not(exit_requested)
  then begin
  { read issued okay, advance the QIO pointer CUR_QIO to the next qio in line }
    if cur_qio = max_qio
      then cur_qio:= 1
      else cur_qio:= cur_qio + 1;
    end;
{ The posting of the QIO clears the QIO_DONE_FLAG, so we 
  must assume that device messages could still need handling. }
device_message_in:= true;
end;



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE POST_DEVICE_WRITE;

  { This procedure will send out a message contained in SEND_BUFFER
    to the device.  On exit, the QIO status will be in 
    SEND_IOSB. }


var
  i: integer;
  fubar: boolean;

Begin
wait(delay_flag,send_delay,ticks);
{ Issue a write qio with wait to the device port.
  The device specific ISSUE_WRITE_QIO will take care of the qio particulars
  including insuring that the qio will execute properly. }
Send_param_list[1]:= sadr(send_buffer);
Send_param_list[2]:= slen(send_buffer);
for i:=3 to 6 do Send_param_list[i]:= 0;
Issue_write_qio(send_iosb,send_param_list);
if Send_IOSB.byt[1] <> is_suc
  then SRVerr(-3,Send_IOSB.byt[1],'Device send error');
{ Note that if we do get a write error, we ignore it, as we should not
  receive an "ACK" from the device, and will try a resend. }
if debug_on then do_write_detail;
{ Since we have sent out the message in the send buffer, we now expect
  to receive an "ACK" back.  So set the WAITING_FOR_ACK boolean true,
  reset the ACK timeout counter, and cancel any outstanding marktimes
  from previous work since we don't want those marktimes piling up. }
waiting_for_ACK:= true;
cmkt(ACK_TMO_flag);
fubar:= direrr('CMKT6',$dsw);
mrkt(ACK_TMO_flag,ACK_timeout,seconds);
fubar:= direrr('MRKT5',$dsw);
end;



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE POST_DEVICE_NAK;

  { This procedure will send out a "NAK" to the device.  The NAK message 
    is contained in NAK_BUFFER.  }


var
  i: integer;
  iosb: io_status_block;

Begin
wait(delay_flag,send_delay,ticks);
{ Issue a write qio with wait to the device port.
  The device specific ISSUE_WRITE_QIO will take care of the qio particulars
  including insuring that the qio will execute properly. }
Send_param_list[1]:= sadr(nak_buffer);
Send_param_list[2]:= slen(nak_buffer);
for i:=3 to 6 do Send_param_list[i]:= 0;
Issue_write_qio(iosb,send_param_list);
if IOSB.byt[1] <> is_suc
  then SRVerr(-16,Send_IOSB.byt[1],'Device NAK send error');
end;



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE POST_DEVICE_ACK;

  { This procedure will send out a "ACK" to the device.  The ACK message 
    is contained in ACK_BUFFER.  }


var
  i: integer;
  iosb: io_status_block;

Begin
wait(delay_flag,send_delay,ticks);
{ Issue a write qio with wait to the device port.
  The device specific ISSUE_WRITE_QIO will take care of the qio particulars
  including insuring that the qio will execute properly. }
Send_param_list[1]:= sadr(ack_buffer);
Send_param_list[2]:= slen(ack_buffer);
for i:=3 to 6 do Send_param_list[i]:= 0;
Issue_write_qio(iosb,send_param_list);
if IOSB.byt[1] <> is_suc
  then SRVerr(-17,Send_IOSB.byt[1],'Device ACK send error');
end;




{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE RESET_LINK;

{ Bring the communication link back to a known state.  Cancel all pending
  input and output.  This is called by INITIALIZE, and then subsequently
  whenever we need to reset the link after a synchronization problem. }

Var
  i: integer;
  fubar: boolean;
  
Begin
{ reset message state booleans and counters, clear buffers }
Send_MSG:= false;
Send_ACK:= false;
Send_NAK:= false;
Waiting_for_ACK:= false;
Device_message_in:= false;
Device_transaction_received:= false;
now_NAKs:= 0;
now_SNAKs:= 0;
now_TMOs:= 0;
sclear(send_buffer);

{ reset message associated flags and any pending marktimes }
cmkt(ACK_TMO_flag);
fubar:= direrr('CMKT5',$dsw);
clef(ACK_TMO_flag);
fubar:= direrr('CLEF5',$dsw);

cur_qio:= 1;
{ cancel any outstanding IO }
for i:=1 to 6 do raw_param_list[i]:=0;
QIOW(io_kil,device_lun,QIO_done_flag,raw_IOSB[cur_qio],raw_param_list);
fubar:= direrr('QIOW2',$dsw);

{ flush typeahead buffer - use a read with immediate timeout }
raw_param_list[1]:= sadr(raw_buffer[cur_qio]);
raw_param_list[2]:= Max_tran_size;
QIOW(io_rlb+tf_tmo,device_lun,QIO_done_flag,raw_IOSB[cur_qio],raw_param_list);
fubar:= direrr('QIOW1',$dsw);

{ clear RAW_IOSB and QIO_DONE_FLAG so that there is no confusion about 
  whether a new message (read) has arrived. }
for i:= 1 to max_qio do
  begin
  raw_IOSB[i].int[1]:= 0;
  raw_IOSB[i].int[2]:= 0;
  end;
clef(QIO_done_flag);
fubar:= direrr('CLEF4',$dsw);

{ setup new series of read QIOs if required to capture incoming messages }
cur_qio:= 1;
if ACT_RLB_raw in MSG_recv_action
  then begin
    for i:= 1 to max_qio do post_device_read;
    { POST_DEVICE_READ sets DEVICE_MESSAGE_IN, but we know better }
    Device_message_in:= false;
    end;

{ log reset }
SRVerr(-15,0,'Comm link to device cleared and restarted');
if debug_on
  then begin
    writeln(deb,'Comm link to device cleared and restarted');
    writeln(deb);
    end;
end;




{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE INITIALIZE;

  { Initialize global variables on start up }

var
  i: integer;
  file_status: integer;
  Task_info: Task_Info_Rec;
  Rad50_task_name: RAD56;
  task: ch6;
  stat: integer;
  buf: packed array [0..200] of char;  {type0 string}

Begin

{ set various booleans }
Exit_requested:= false;
Message_packet_received:= false;
Message_in_queue:= false;
Monitor_on:= false;
Debug_on:= false;

{ clear cumulative counters }
cum_NAKs:= 0;
cum_SNAKs:= 0;
cum_ACKs:= 0;
cum_TMOs:= 0;

{ clear internal message queue - a ring buffer - make list start and end
  pointers the same value }
internal_msg_start_ptr:= 1;
internal_msg_end_ptr:= 1;

{ initialize the current read QIO pointer - also done in RESET_LINK }
cur_qio:= 1;

{ put out a CR on TI: just to make things look nice for startup messages }
writeln;

{ open and verify device communication lun on port DEVICE_PORT_NAME  -
  at this time we derive DEVICE_PORT_NAME by using the last to characters
  (digits) of the installed task name, appending them to the prefix
  defined in DEVICE_LOGICAL_PREFIX.  The last digit is also used 
  as the common record number to be used in the device common. }

GTSK(Task_info);
IF $DSW = success 
  then begin
    { Convert task name to ascii, and get last two characters.  
      Verify that they are digits. }
    Rad50_task_name := Task_info.name;
    {Convert rad50 name to ascii}
    CR56TA(Rad50_task_name, Task_name);
    i:= 6;
    while (i > 2) and (task_name[i] = ' ') do i:= i-1;
    { I now points to last two characters in task name - task name
      must at least be two characters}
    if (task_name[i-1] in ['0'..'9']) and (task_name[i] in ['0'..'9'])
      then begin
        { last two characters are digits.  Now create DEVICE_PORT_NAME
          and pointer to DEVICE common record. }
        sassign(device_port_name,device_logical_prefix);
        schconcat(device_port_name, task_name[i-1]);
        schconcat(device_port_name, task_name[i]);
        schconcat(device_port_name,':');
        end
      else begin
        { last two characters are non-digits or only a 1 char task name }
        sassign(buf, 'Invalid task name - "');
        sconcat(buf, task_name);
        schconcat(buf, '"');
        SRVerr(-9, 0, buf);
        exit_requested:= true;
        end;
    end
  else begin
    { could not get task name, fatal error }
    i:= $DSW;
    sassign(buf,'GTSK directive error');
    SRVerr(-10, i, buf);
    exit_requested:= true;
    end;

if not(exit_requested)
  then begin
    { setup the device port }
    device_lun:= 1;
    reset(device_port,device_port_name,,file_status);
    if file_status < 0
      then begin
        {failed to open device port}
        sassign(buf,'Cannot find device port "');
        sconcat(buf,device_port_name);
        schconcat(buf,'"');
        SRVerr(-1, file_status, buf);
        exit_requested:= true;
        end
      else begin
        {device port found okay, assign lun number var (first 
         file opened, so must be lun 1) and attach}
        for i:= 1 to 6 do Raw_param_list[i]:= 0;
        QIOW(io_att,device_lun,QIO_done_flag,Raw_IOSB[cur_qio],Raw_param_list);
        if direrr('QIOW6',$dsw) then exit_requested:= true;
        If Raw_IOSB[cur_qio].int[1] <> 1 
          then begin
            { Failed to attach device port, something is not right.  We cannot
              go on, so we exit. }
            SRVerr(-5,Raw_IOSB[cur_qio].int[1],'Attach of device port failed');
            exit_requested:= true;
            end 
          else begin
           { device port attached okay, the read QIOs to pick up 
             commands as they come in will be setup in RESET_LINK. }
           end;
        end;
    end;

{ set up AST notification of message packet receipt - results in setting
  of Packet_in_flag }
srda(Packet_in_flag);
if direrr('SRDA1',$dsw) then exit_requested:= true;

{ clear out any outstanding message packets }
stat:= success;
sclear(task);
While stat >= success do
  Begin
  RcvMsg(task, new_message_packet, stat);
  end;

{ clear significant event notification task list  - at this time, one task }
sclear(Notify_task);

{ announce startup }
if not(exit_requested)
  then begin
    write(task_name,': ',server_desc,' starting up on port ');
    swrite(output,device_port_name);
    writeln;
    end;

{ setup monitor and debug if needed }

if (init_debug_on) and (not(exit_requested))
  then begin
    { turn debug on device TI: }
    debug_on:= true;
    stat:= 0;
    rewrite(deb, 'TI:', , stat);
    { if open fails, turn off debug }
    if stat < 0 then debug_on:= false;
    end;

{ do any device specific initialization required }
if not(exit_requested) then Initialize_specific;

if (init_monitor_on) and (not(exit_requested))
  then begin
    { Turn monitor on device TI: - note we wait until after the 
      INITIALIZE_SPECIFIC setup is done, as monitor no doubt shows info
      that is initialized in that routine. }
    monitor_on:= true;
    stat:= 0;
    rewrite(mon, 'TI:', , stat);
    { if open fails, turn off monitor }
    if stat < 0 
      then monitor_on:= false
      else monitor_startup;
    end;

{ reset the device link }
if not(exit_requested) then Reset_link;

{ announce startup is complete }
if not(exit_requested)
  then begin
    writeln(task_name,': ',server_desc,' startup is complete');
    writeln;
    end;

End;




{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE CHECK_FOR_DEVICE_MESSAGES;

{ See if the next raw QIO expected to complete has done so - it is pointed
  at by CUR_QIO.  We look at the IOSB for this info - the associated event
  flag is not reliable by this time.  If the QIO has completed, see if it
  is an ACK or NAK, or whether it is a transaction.  If it is a transaction,
  see if one is already pending (not normally possible), and if so,
  discard it and drop communications. (do not ACK).   Note that we read
  all outstanding messages before going on.  By the protocol definition,
  we should have, at most, a ACK/NAK and a MSG (transaction).}

Var
  i: integer;
  fubar: boolean;
  drop_link: boolean;
  resend_after_NAK: boolean;
  ACK_came_in: boolean;

{ local procedures... }

{~~~~~~~~~}
 PROCEDURE CHECK_NAK;	{Local procedure }

{ We received a NAK.  See if we were expecting an ACK.  If so, retransmit
  the message to the device.  After six consecutive NAKs, we will give up
  and not retransmit.  The device will drop the link, and we will start
  over from scratch.  If we were not expecting an ACK, ignore NAK. }

Begin
{ NAK received, were we expecting an ACK? }
if (waiting_for_ACK) 
  then begin
    { We were looking for an ACK, but got a NAK instead.  That means
      we need to send the message.  Since no new sends are posted
      while we wait for ACK, we can just set up SEND_BUFFER to
      send again. }
    waiting_for_ACK:= false;
    now_NAKs:= now_NAKs + 1;
    cum_NAKs:= cum_NAKs + 1;
    if debug_on then writeln(deb,'Expecting ACK, but NAK #',
                       now_NAKs:1,' received');
    if now_NAKs >= NAK_max
      then begin
        {We have now had more than NAK_MAX consecutive NAKs.  
         We give up and reset the link. }
        drop_link:= true;
        if debug_on 
          then writeln(deb, 'Exceeded NAK limit of ',NAK_MAX:1,
                 ', giving up send attempt');
        SRVerr(-12,NAK_max,'Too many NAKs from device');
        cmkt(ACK_TMO_flag);
        fubar:= direrr('CMKT3',$dsw);
        clef(ACK_TMO_flag);
        fubar:= direrr('CLEF3',$dsw);
        now_NAKs:= 0;
        end
      else begin
        { Still have not exceeded our NAK limit.  We resend 
          message again.  Note that we do not check the 
          status.  Its too late now.  If
          it fails to even go out, our comm link is going down.}
        resend_after_NAK:= true;
        end;
    end
  else begin
    { We got a NAK, but we were not even expecting an
      ACK - log the error and reset the link.}
    drop_link:= true;
    SRVerr(-7,0,'NAK from device out of sequence');
    if debug_on then writeln(deb,'Unexpected NAK ',
                         'received, ignored');
    end;
{ See if we need to post a new read QIO to take this ones place }
if ACT_RLB_raw in NAK_recv_action then post_device_read;
end;


{~~~~~~~~~}
 PROCEDURE CHECK_TRANSACTION;	{Local procedure }

{ Message received contains a transaction.  See if
  there was a format error (bad checksum or the like), and if so, send
  out a NAK to request retransmit.  If message is formatted correctly,
  make sure that we do not already have a transaction pending.  If we do,
  discard the message and do not ACK - something has gone wrong with
  our communication.  The device will drop the link and we will have to
  start over. }

Begin
{ Check out transaction IO status }
if raw_iosb[cur_qio].byt[1] in [ie_ver,ie_dao,ie_bcc]
  then begin
    { hardware/OS driver error }
    SRVerr(-2,raw_iosb[cur_qio].byt[1],'device port read error');
    end;
if debug_on then do_read_detail;
if raw_iosb[cur_qio].byt[1] in [is_suc,is_tmo]
  then begin
    { a transaction has arrived from device, see if can be
      accomodated }
    if device_transaction_received 
      then begin
        { Sorry, no room at the inn.  We log error, and do not
          ACK message, dropping it on the floor.  The protocol states
          that we will not receive another transaction before the 
          previous one is ACKed.  Since this violates the protocol,
          we shall drop the link, and cancel any pending send operations. }
        drop_link:= true;
        SRVerr(-11,raw_IOSB[cur_qio].int[2],'MSG from device out of sequence');
        if debug_on 
          then writeln(deb,'MSG from device out of sequence, ignored');
        end
      else begin
        { Transfer the transaction from the RAW_BUFFER/RAW_IOSB to the 
          RECEIVE_BUFFER/RECEIVE_IOSB for processing using the 
          MODIFY_TRANSACTION_SPECIFIC procedure.  The procedure can also
          provide any device specific massaging of the data.  
          When MODIFY_TRANSACTION_SPECIFIC exits, the IO status is
          RECEIVE_IOSB.BYT[1] must be IS_SUC, or the transaction will
          be discarded, and a retransmission requested from device. }
        Modify_transaction_specific;
        if receive_iosb.byt[1] = is_suc
          then begin
            { The transaction is correctly formatted and transferred to
              the RECEIVE_BUFFER.  Remember that we have not yet ACKed
              the transaction, as we first need to insure that it is
              processed for data base integrity.  After that it will be
              ACKED. }
            device_transaction_received:= true
            end
          else begin
            { Something was detected in the format of the transaction 
              before or while it was being transferred to the RECEIVE_BUFFER.
              The cause of the problem is in RECEIVE_RESULT.  We discard
              this transaction, and request a retransmission. }
            SRVerr(-13,receive_iosb.byt[1],'device message format error');
            send_NAK:= true;
            end;
        end;
    end
  else begin
    { send out a NAK, and see if we can get a good
      transmission this time. }
    send_NAK:= true;
    end;
{ See if we need to post a new read QIO to take this ones place }
if ACT_RLB_raw in MSG_recv_action then post_device_read;
if send_NAK
  then begin
    { We rejected this transaction, and are planning on sending out a
      NAK, we need to make sure that we have not exceeded our limit
      of consecutive send NAKs (SNAK_max).  If we have, we give up and
      drop the link. }
    now_SNAKs:= now_SNAKs + 1;
    cum_SNAKs:= cum_SNAKs + 1;
    if now_SNAKs >= SNAK_max
      then begin
        {We have now had more than SNAK_MAX consecutive SNAKs.  
         Cancel the NAK send command, give up and reset the SNAK counter
         and reset the link. }
        drop_link:= true;
        send_NAK:= false;
        now_SNAKs:= 0;
        if debug_on 
          then writeln(deb, 'Exceeded send NAK limit of ',SNAK_MAX:1,
                 ', giving up send attempt');
        SRVerr(-12,NAK_max,'Too many consecutive NAKs sent to device');
        end
      else begin
        { Still have not exceeded our NAK limit.  We send out NAK to
          device to try and get a retransmit of the transaction. }
         if debug_on 
           then writeln(deb,'Unable to handle transaction, sending NAK #',
                   now_SNAKs:1);
        end;
    end
  else begin
    { Transaction came in okay (or we must reset the link).  We can reset
      of send NAK counter. }
    now_SNAKs:= 0;
    end;
end;


{~~~~~~~~~}
 PROCEDURE CHECK_ACK;	{ Local procedure }

{ We received an ACK.  See if we were expecting one.  If we were, note
  it and reset the timeout counter.  If we were not, ignore the ACK. }

Begin
if (waiting_for_ACK) 
  then begin
    { received the ACK we were looking for }
    ACK_came_in:= true;
    waiting_for_ACK:= false;
    cmkt(ACK_TMO_flag);
    fubar:= direrr('CMKT1',$dsw);
    clef(ACK_TMO_flag);
    fubar:= direrr('CLEF1',$dsw);
    if ACT_message_with_ACK in ACK_recv_action
      then begin
        { a message accompanies the ACK.  We set up so that it
          will be processed.  If we need
          to repost a QIO, CHECK_TRANSACTION will do it. }
        Check_transaction;
        end
      else begin
        { this is just a plain ACK, repost a new QIO if needed. }
        { See if we need to post a new read QIO to take this ones place }
        if ACT_RLB_raw in ACK_recv_action then post_device_read;
        end
    end
  else begin
    { Received an ACK, but we were not waiting for one.  Ignore it and reset
      the link. }
    drop_link:= true;
    SRVerr(-6,0,'ACK from device out of sequence');
    if debug_on then writeln(deb,'Unexpected ACK received, ignored');
    { See if we need to post a new read QIO to take this ones place }
    if ACT_RLB_raw in ACK_recv_action then post_device_read;
    end;
end;


{ ~~~~~~~~~~~~~~~~~~~}
{ Main procedure ... }

Begin
{ We set the boolean DROP_LINK false at start.  If any condition comes up
  that violates the communciation protocol, we indicate that by setting
  the boolean true.  At the end of this procedure, if DROP_LINK is
  true, we will reset the link by canceling our pending send output. }
drop_link:= false;
ACK_came_in:= false;
resend_after_NAK:= false;

{ Check the "current" QIO in the ring buffer of QIOs.  If the IOSB 
  remains at zero, it is still pending.  If the IOSB is non-zero, the
  QIO is complete, and we can start processing it.  Since a transaction
  might already be queued before an awaited ACK/NAK, we will need to 
  process QIOs until the ACK/NAK is found.  This normally, should not
  present a problem, as the device should wait before sending another
  transaction until the ACK for the currently queued transaction is
  received.  However, in the case of devices that do not wait for
  ACKs, we could fall behind.  In the worst case, we will have to
  drop the excess transactions on the floor. }
If Raw_IOSB[cur_qio].int[1] = 0
  then begin
    { QIO still pending, no completed messages at this moment.  We set
      DEVICE_MESSAGE_IN false to indicate that we looked and nothing
      found.  We can then wait for the flag to set to indicate that a
      QIO completed.  Of course, between now and the WHILE that follows,
      the QIO could complete.  But, no problem, as we will handled the
      completed QIO.}
    if debug_on
      then begin
        writeln(deb,'No device messages received');
        writeln(deb);
        end;
    device_message_in:= false;
    end
  else begin
    { Now we check the queue for completed QIOs (device messages 
      that have come in),  and read messages that have come in.  
      In cases of the ACK/NAK protocol, there should be, at most, 
      an ACK/NAK and a transaction (or visa versa) out there, as the
      device will wait for an ack to the current transaction before
      sending more.  In the case of a device that does not wait
      for ACKs, there could be more.  For that reason, we will process
      QIOs until an ACK is received (or no more transactions), and
      if multiple transactions appear before the ACK, we will just
      have to drop the excess on the floor. }
    Repeat    
      { QIO has completed. }
      if debug_on
        then begin
          { note that a raw message received and detail the IO status }
          writeln(deb,'Device message received, IOSB1[lo/hi]=',
             raw_iosb[cur_qio].byt[1]:1,'/',raw_iosb[cur_qio].byt[2]:1,
             ', IOSB2=',raw_iosb[cur_qio].int[2]:1);
          end;
      { Do any device specific intepretation of the IO status block and
        message to validate checksum, ACK/NAK status, etc }
      Modify_IOSB_specific;
      if (raw_iosb[cur_qio].byt[1] = 1) and (raw_iosb[cur_qio].byt[2] = 6)
        then begin
          { ACK received, see if it is expected. }
          Check_ACK;
          end
        else begin
          { see if it is a NAK }
          if (raw_iosb[cur_qio].byt[1] = 1) and (raw_iosb[cur_qio].byt[2] = 21)
            then begin
              { NAK received, see if it is expected. }
              Check_NAK;
              end
            else
              begin
              { must be a device transaction }
              Check_transaction;
              end;
          end;
      if debug_on then writeln(deb);
      if not(drop_link)
        then begin
          { no protocol violations, see if an ACK or NAK came in }
          if ACK_came_in
            then begin    
              { Expected ACK to current send arrived on time }
              now_NAKs:= 0;
              now_TMOs:= 0;
              cum_ACKs:= cum_ACKs + 1;
              if debug_on then writeln(deb,'Expected ACK received');
              end;
          if resend_after_NAK
            then begin
              { NAK came in response to current send.  We have not yet
                exceeded our NAK limit, so resend the transaction. }
              if debug_on 
                then writeln(deb, 'Resending message after NAK receipt');
              if ACT_WLB_send in NAK_recv_action 
                then Post_device_write;
              end;
          end;
      until (drop_link) or (Raw_IOSB[cur_qio].int[1] = 0) 
          or (device_transaction_received);
    end;
if drop_link
  then begin
    { A protocol violation was detected.  Message out of sequence or
      ACK/NAK out of sequence.  Reset the link by canceling any pending
      sends (ACK/NAK/MSG) and any pending transaction processing.
      If the device is not satisfied (missing ACK or whatever), it will
      drop the link (go to local).  The connection can then be 
      resestablished by setting the device back to remote. }
    reset_link;
    end;
End;




{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE PUT_INTERNAL_MSG(var str: packed array [slow..shigh:integer]
   of char; var stat: integer);

{ Take the device command in STR and package it up in a device command
  message packet for later processing on a FIFO basis. }

Var
  hold_ptr: integer;
  msg: message_packet_type;
 
Begin
stat:= no_msg;
{ increment list end pointer and store in temp var HOLD_PTR }
if internal_msg_end_ptr = max_internal_msg
  then hold_ptr:= 1
  else hold_ptr:= internal_msg_end_ptr + 1;
if hold_ptr <> internal_msg_start_ptr
  then begin
    { List end pointer has not yet hit list start pointer, so there is
      still room in the queue, so we store away the message. }
    msg.id:= pk_send_as_is;
    svassign(msg.content,str);
    internal_msg_queue[hold_ptr]:= msg;
    internal_msg_end_ptr:= hold_ptr;
    message_in_queue:= true;
    stat:= success;
    end;
end;



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE GET_INTERNAL_MSG(var msg: message_packet_type; var stat: integer);

{ Check the internal message queue, and if a message is present, return the
  next one in line in BUF, and SUCCESS in STAT.  If no messages queued,
  return STAT as NO_MSG. }

Begin
stat:= no_msg;
if internal_msg_start_ptr <> internal_msg_end_ptr
  then begin
    { The list start pointer is not equal to the list end pointer, so there
      is one or messages in the queue (a ring buffer).  The list start
      pointer points to the oldest message, and thus the next one to be
      processed in this FIFO queue. }
    stat:= success;
    msg:= internal_msg_queue[internal_msg_start_ptr];
    if internal_msg_start_ptr = max_internal_msg
      then internal_msg_start_ptr:= 1
      else internal_msg_start_ptr:= internal_msg_start_ptr + 1;
    end;
end;   



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE PROCESS_DEVICE_TRANSACTION;

  { At this level, we have received a valid message from the device, and
    now we can examine the transaction to determine what it is
    and if it is recognized.  The message has not yet been ACKed by us.
    The message was correctly formatted, we just need to process it
    to insure that we meet our audit trail standards before ACKing it.
    We are not guaranteed to finish our processing on this pass, since an
    outgoing transaction to the device might be queued, and that
    transaction may have to be handled first.  In most cases, we can
    handle the transaction in one pass.  In all cases, we must insure
    that the ACK (or NAK) is done in a timely manner, or the device
    will timeout and drop the link. }

var
  processing_done: boolean;


Begin  { procedure process_device_transaction }
{ Do the device specific transaction processing }
process_transaction_specific(processing_done);
{ If processing is completed okay (PROCESSING_DONE is TRUE, we signal that 
  we are ready to receive another transaction.  Otherwise, we hold off.}
If processing_done 
  then begin
    { Processing is complete, we send ACK.  Note that the processing may
      have queued a response to the device.  However, the main loop of
      the program is structured so that the ACK will be send first, and
      then the response transaction. }
    send_ACK:= true;
    device_transaction_received:= false;
    if debug_on then writeln(deb,'  Transaction processed, send out ACK');
    end
  else
    begin
    if debug_on then writeln(deb,'  Tranaction processing deferred for now');
    end;
if debug_on then writeln(deb);
end;

  


{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE PROCESS_PACKET;

  { A message packet was received.  Examine it and do any action 
    required.  We will find the packet in NEW_MESSAGE_PACKET.
    This routine is not called until all reads and writes to the device
    are complete.  Thus if we need to generate a write to the device in
    response to a message packet, we are free to do so, and SEND_BUFFER
    is available for that purpose.  We split the message packet
    processing into two parts - device generic (done here), and device
    specific (called here but done above). }

Var
  packet_recognized: boolean;
  packet_desc: ch20;
  file_status: integer;


Begin  { procedure Process_packet }
if debug_on
  then begin
    { display field portion of packet that came in.  Display of further
      info in the value portion needs to be handled by the individual
      process block as it may be nonprintable.  Unconditional writeln
      will be done at the end of this routine, so we do a write here,
      and add more as we process the individual case.}
    cpitas(New_message_packet.id, packet_desc);
    supper(packet_desc);
    writeln(deb,'"',packet_desc,'" msg pkt recvd...');
    cpstas(New_message_packet.sub, packet_desc);
    writeln(deb,'   with sub type subset [',packet_desc,']');
    end;
{ we assume we will recognize the packet }
packet_recognized:= true;

{ handle the general case packets }
With New_message_packet do
begin
Case id of
  pk_debug:
    begin
    if ps_start in sub
      then begin
        { the "debug" mode is recognized at this time - DEB_LEVEL ignored }
        if debug_on 
          then 
            begin
            { finish up output to current device, turn on new device }
            writeln(deb,'send to ',deb_device);
            close(deb);
            end;
        debug_on:= true;
        file_status:= 0;
        rewrite(deb, deb_device, , file_status);
        { if open fails, turn of debugging - too late to go back to old device }
        if file_status < 0 then debug_on:= false;
        end;
    if ps_stop in sub
      then begin
        { finish up write and close deb file }
        if debug_on then writeln(deb,'debug turned off');
        close(deb);
        debug_on:= false;
        end;
    end;
  pk_monitor:
    begin
    if ps_start in sub
      then begin
        { the "monitor" mode is recognized at this time, level ignored }
        if debug_on then writeln(deb,'send to ',mon_device);
        if monitor_on
          then 
            begin
            { finish up output to current device, turn on new device }
            Monitor_clear;
            close(mon);
            end;
        monitor_on:= true;
        file_status:= 0;
        rewrite(mon, mon_device, , file_status);
        { if open fails, turn off monitor - too late to go back to old device }
        if file_status < 0 
          then monitor_on:= false
          else monitor_startup;
        end;
    if ps_stop in sub
      then begin
        { finish up write and close mon file }
        Monitor_clear;
        close(mon);
        if debug_on then writeln(deb,'monitor turned off');
        monitor_on:= false;
        end;
    end;
  pk_orderly_abort:
    begin
    { whatever they say.. }
    if debug_on then writeln(deb,'  exit begun');
    exit_requested:= true;
    end;
  otherwise
    begin
    { all other packets are ignored - either specific or unrecognized }
    packet_recognized:= false;
    end;
  end; { case }
  end; { with }

{ now the device specific packet processing }
Process_packet_specific(packet_recognized);

if (not(packet_recognized)) and (debug_on)
  then writeln(deb,'  ignored (we don''t do windows)');
if debug_on then writeln(deb);
message_packet_received:= false;
End;








{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE CHECK_FOR_MESSAGE_PACKETS;

  { This procedure is called when we are ready to process a message packet,
    and we believe that at least one packet is pending.  We must account
    for the case where we think a packet is pending, but it is not. 
    The internal message queue is used to hold device commands
    or other messages that could not be sent right away (send already 
    pending, or more than on message to go out at a time).  We check that
    internal queue first before checking the external (system) queue.
    On exit, the packet, if any, is found in NEW_MESSAGE_PACKET, a 
    variant record. }

  { AT THIS TIME, INTERNAL QUEUE DOES NOT WORK RIGHT }

var
  buf: packed array [0..200] of char;
  msg: message_packet_type;
  rtask: Rad56;
  len, i: integer;
  stat: integer;
  cmd: ch6;
  fubar: boolean;

Begin
{ First look in the internal queue  - AT THIS TIME, NOT ACTIVATED }
{Get_internal_msg(msg,stat);}
{ We clear the PACKET_IN_FLAG before we do the packet read.  If we find
  no packets, then we are assured that the PACKET_IN_FLAG will get
  set only if a new packet arrives.  If we cleared the flag after the
  packet read, we would have a window where a packet could arrive (after
  RCVMSG but before CLEF execution) where we would clear the flag even
  though a packet is pending. }
CLEF(Packet_in_flag);
fubar:=direrr('CLEF2',$dsw);
Sclear(New_packet_task); { we receive from anyone }
RcvMsg(New_packet_task, New_message_packet, stat);
if stat >= success
  then begin
    { a message packet was received }
    message_packet_received:= true;
    { We reset the Packet_in_flag since we must assume that there are
      more messages pending until proven otherwise. }
    SETF(Packet_in_flag);
    fubar:=direrr('SETF2',$dsw);
    end
  else begin
   { An error - could mean no messages, or could mean that there was a
     message, but it was too large for our buffer }
   if stat = no_msg
     then begin
       { No messages out there.  We set MESSAGE_IN_QUEUE to false to
         indicate that fact. }
       message_in_queue:= false;
       end
     else begin
       { Some other directive error - probably we received a message
         that was longer than we recognize.  We will log error and
         ignore message. }
       fubar:= direrr('RCVMSG3',stat);
       { We reset the Packet_in_flag since we must assume that there are
         more messages pending until proven otherwise. }
       SETF(Packet_in_flag);
       fubar:= direrr('SETF2',$dsw);
       end;
   end;
if message_packet_received
  then begin
    { A message packet from some one is ready to be processed.  }
    if debug_on then writeln(deb, 'Message packet received from "',
                        New_packet_task,'"');
    end
  else
    begin
    if debug_on
      then begin
        writeln(deb,'No msg packets outstanding');
        writeln(deb);
        end;
    message_packet_received:= false;
    end;
End;



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE SEND_OUT_ACKNAK;

  { Send out an ACK or NAK response to the device.  Caller has already checked
    to make sure that SEND_ACK or SEND_NAK is true.}

Begin
if (send_ACK) and (send_NAK) 
  then SRVerr(-8,0,'program logic: send_ACK/NAK both true');
{ If both SEND_ACK and SEND_NAK is set true, we log error, and then do
  then SEND_NAK, and ignore the SEND_ACK. }
if send_NAK 
  then begin
    { we now need to execute the action specified for NAK action}
    if debug_on then writeln(deb,'Send out NAK');
    if ACT_WLB_NAK_buffer in NAK_send_action 
      then Post_device_NAK;
    if ACT_WLB_send in NAK_send_action 
      then Post_device_write;
    end
  else begin
    { we now need to execute the action specified for ACK action}
    if debug_on then writeln(deb,'Send out ACK');
    if ACT_WLB_ACK_buffer in ACK_send_action 
      then Post_device_ACK;
    end;
{ We do not worry about the success of the send.  Writes don't fail unless
  the system is going to pot, and if the system is going to pot, this
  program won't be around too much longer to worry about it. }
send_ACK:= false;
send_NAK:= false;
end;



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE SEND_OUT_DEVICE_MESSAGE;

  { Send out a message (transaction) to the device.  This does not
    include ACKs or NAKs.  The message text is in the SEND_BUFFER. 
    Caller has already verified that SEND_MSG is true.}

Begin
{ PREP_MESSAGE_SPECIFIC will allow us to massage the outgoing message to the
  device if there is a need due to device specific considerations, also
  it will issue any debug messages needed. }
Prep_message_specific;
{ post the appropriate type of send action }
if ACT_WLB_send in MSG_send_action
  then Post_device_write;
send_MSG:= false;
end;



{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE NAP_TIME;

  { We have a case where multiple events could occur, and not all of these
    events may be serviced during one cycle.  For that reason, we use
    multiple event flags, one tied to each event.  These event flags are
    cleared only when that event has been serviced.  If the event cannot
    be serviced to completion, then a marktime must be set up to insure
    that the flag associated with that event will be set within a 
    given time interval. 
  }

Var
  fubar: boolean;
  nap_approved: boolean;
  work_pending: boolean;

Begin
{ assume its okay to go to sleep unless proven otherwise. }
nap_approved:= true;

{ ACK or NAK can be sent out without delay }
if send_ACK then nap_approved:= false;
if send_NAK then nap_approved:= false;

{ check for possible message in from device }
if device_message_in then nap_approved:= false;

{ send a message out to device if we can }
if ((send_MSG) and (not((send_ACK) or (send_NAK) or (waiting_for_ACK))))
  then nap_approved:= false;

if nap_approved
  then begin
    { Looks like we get to sleep.  Now its just up to the event flags. }
    { Stop if all of the event flags associated with significant events
      are clear.  This means that there is nothing to do.  If one or more
      of those flags are set, there is work to do.  Because all events
      do not have equal priority, we need to choose the events that we
      are waiting for.  In particular, message packets have low priority,
      and so if we are waiting for an ACK from the device, we ignore that
      event until the ACK comes in or a timeout is declared. }
    { NOTE ALSO, that we know a event occurred and needs servicing.  However,
      we do not know WHICH event occurred.  Furthermore, even if we checked
      the individual flags after, we are not assured that the event will not
      occur between the time we check the flag and the time we come back here
      for a nap.  So, we assume the worst and check all events that could have
      occurred.  In the routines that check for each event, if the event is
      serviced or did not occur, the flag is cleared.  By the time we return
      to take a nap, if the event occurred again, the flag will be set.  We
      are assured that there are no "windows" where an event could occur and
      not be serviced. }
    if waiting_for_ACK
      then begin
        { No device sends can be done while waiting for ACK from device, so it
          doesn't make any sense to process packets.  So we just wait for
          the ACK to come in or a timeout to occur.  Since we have a maximum
          time that we will wait, a pending message packet will get
          serviced.  It'll just take awhile. }
        stlo([QIO_done_flag, ACK_TMO_flag]);
        fubar:= direrr('STLO1',$dsw);
        device_message_in:= true;
        end
      else begin
        { Not waiting for ACK, so we just wait for incoming device messages or
          message packets.  We can now finally do any background work that
          we need to to.}
        Background_work_specific(work_pending);
        { If the specific work found, then WORK_PENDING is returned TRUE,
          and we skip the nap.  }
        if not(work_pending)
          then begin
            stlo([QIO_done_flag, Packet_in_flag]);
            fubar:= direrr('STLO2',$dsw);
            end;
        message_in_queue:= true;
        device_message_in:= true;
        end;
    end;
End;




{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE SHUTDOWN;

  { Orderly shutdown }

Var
  i: integer;
  fubar: boolean;

Begin
for i:=1 to 6 do Raw_param_list[i]:=0;
QIOW(io_kil,device_lun,send_flag,Raw_IOSB[cur_qio],Raw_param_list);
fubar:= direrr('QIOW7',$dsw);
QIOW(io_det,device_lun,send_flag,Raw_IOSB[cur_qio],Raw_param_list);
fubar:= direrr('QIOW8',$dsw);
if debug_on 
  then begin
    writeln(deb);
    writeln(deb,Server_desc,' shutdown complete');
    close(deb);
    end;
if monitor_on 
  then begin
    monitor_clear;
    end;
{ announce shutdown }
writeln(task_name,': ',server_desc,' shutdown is complete');
End;




{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
PROCEDURE CHECK_ACK_WAITING;

  { See if the expected ACK is going to be a no show }

Var
  fubar: boolean;
  resend_after_TMO, drop_link: boolean;

Begin
drop_link:= false;
resend_after_TMO:= false;
if RDEF(ACK_TMO_FLAG) = true
  then begin
    { We were looking for an ACK, but got no response (timeout) instead.  
      If we have not exceeded the number of consecutive timeouts allowed,
      we will resend the message.  If we have exceeded that limit, then
      we give up, and reset the link.  Since no new sends are posted
      while we wait for ACK, we can just set up SEND_BUFFER to
      send again. }
    waiting_for_ACK:= false;
    now_TMOs:= now_TMOs + 1;
    cum_TMOs:= cum_TMOs + 1;
    if debug_on then writeln(deb,'Expecting ACK, but TMO #',
                       now_TMOs:1,' received');
    if now_TMOs >= TMO_max
      then begin
        {We have now had more than TMO_MAX consecutive TMOs.  
         We give up and reset the link. }
        drop_link:= true;
        if debug_on 
          then writeln(deb, 'Exceeded TMO limit of ',TMO_MAX:1,
                 ', giving up send attempt');
        SRVerr(-24,TMO_max,'Too many timeouts from device');
        cmkt(ACK_TMO_flag);
        fubar:= direrr('CMKT4',$dsw);
        clef(ACK_TMO_flag);
        fubar:= direrr('CLEF6',$dsw);
        now_TMOs:= 0;
        end
      else begin
        { Still have not exceeded our TMO limit.  We resend 
          message again.  Note that we do not check the 
          status.  Its too late now.  If
          it fails to even go out, our comm link is going down.}
        resend_after_TMO:= true;
        end;
    { See if we need to post a new read QIO to take this ones place }
    if ACT_RLB_raw in TMO_recv_action then post_device_read;
    if resend_after_TMO
      then begin
        { TMO in response to current send.  We have not yet
          exceeded our TMO limit, so resend the transaction. }
        if debug_on 
          then writeln(deb, 'Resending message after timeout');
        if ACT_WLB_send in TMO_recv_action 
          then Post_device_write;
        end;
    if drop_link
      then begin
        { too many TMOs, try clearing and resetting the link }
        reset_link;
        end;
    end
  else begin
    { Timeout has not yet occurred.  We keep waiting. }
    fubar:= direrr('RDEF1',$dsw);
    end;
end;




{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

Begin  {Main LOOP}

{ Get things rolling }
Initialize;

While not(exit_requested) do
  begin

  { Highest priority is receiving device messages.  
    Only one ACK/NAK and/or MSG can be pending at any one time, per
    the protocol.  If we receive additional ACK/NAK or MSG before
    previous one is processed, they will be rejected.  If the message
    was not properly formatted and recorded, or a timeout occured on
    an expected receipt, the SEND_NAK is set true
    to reply with NAK.  If we receive a NAK, we resend the pending
    message to the device.}
  Check_for_device_messages;

  { Second priority is processing device transactions.  If 
    DEVICE_TRANSACTION_RECEIVED is true, the message was properly
    formatted.  They will then be processed and then ACKed.  Since
    the message must be ACKed in a timely fashion, the processing
    is limited to work that can be done quickly and insures that
    the audit trail is secure (transaction recorded).  It may take
    several passes through this routine to complete the processing
    if other work is already pending. }
  if device_transaction_received then process_device_transaction;

  { Next priority is sending ACK or NAK out.  Device should be waiting for
    this, and not sending any new transaction. }
  if ((send_ACK) or (send_NAK)) then Send_out_ACKNAK;

  { Next we must see if must continue waiting for an ACK.  If the
    ACK_TMO_FLAG has set, then the ACK never showed.}
  if waiting_for_ACK then Check_ACK_waiting;

  { Next priority is sending device messages.  We cannot send a message
    if we are waiting to send an ACK or NAK, as we must do that first.
    Nor can we send a message if we are waiting to
    receive an ACK (or NAK as the case may be), as that 
    means we have a send pending still, and the rules demand only one send
    pending at a time.}
  if ((send_MSG) 
         and (not((send_ACK) or (send_NAK) or (waiting_for_ACK))))
    then Send_out_device_message;


  { Next in priority is message packet processing.  We accept one packet at
    a time, and process it.  Since we must be ready to post a message
    to the device, we make sure that we are clear to ready a message.
    Likewise, we defer to transaction processing if that is pending. }
  if not((send_ACK) or (send_NAK) or (send_MSG) or (waiting_for_ACK)
             or (device_transaction_received))
    then begin
      { Process any pending packet.  If no pending packet, then
        get a new packet, if any, and process }
      if message_packet_received 
        then Process_packet
        else begin
          Check_for_message_packets;
          if message_packet_received then Process_packet
          end;
      end;

  { Now we see if there is anything to do, and if not, we take a nap until
    there is.  NAP_TIME will do the checking to see if nap is to be allowed. }
  Nap_time;

  end;

{ Go home for the weekend. }
Shutdown;  

end.  {main}
