FSC-0010

RESYNC, a sealink protocol enhancement by Henk Wevers  2/0
==========================================================


What is resync (recovarable sealink)
------------------------------------

Resync is a protocol enhancement on Sealink by Sea corporation
that allows the protocol to pickup broken transfers were it was
interrupted. The coding overhead is very minor because almost all
routines needed are already part of most sealink implementations.

As a sideeffect transmissions of exact duplicate files (from whatever
source) will only result in the two programs exchanging EOT and thus
saving a lot of transfertime and costs.

The protocol
-------------

The capability of doing ackless sealink
is signalled by the SENDER by having
byte  41 (1 based) in block 0 of a sealink file transfer being <> 0.
Recovery is signalled in the sameway by byte 42 <> 0.

Recoverable sealink starts off like normal (unrecoverable sealink).
After the receiver has received block zero without errors the
receiver checks for a duplicate filename in its incoming file
directory. When a match is found the time and datestamp are checked
and when they match the resync procedure is started otherwise`
the transfer goes on like normal.

Recovery procedure:

RECEIVER
--------

The receiver sends the following block to the
sender:

<sync> <blocknumber> <eot> <crclow> <crchigh>

sync = $22
blocknumber: ascii , number of block to resume with, 1 based
eot = $03
crc as usual

The reason this form is choosen is that it is the same block as used
for passing the filename in sealink based filerequests so the code
was already there.

now the receiver waits ontil the line dies (looks for a 1 sec pause)
then sends $05 and waits for ACK or NAK from the sender. If nak is
received the recovery procedure is restarted . After a given number
of failed tries the session is aborted.

After an ACK the receiver 'seeks' at the given
block and resumes sealink transfer.


SENDER
------

The sender has the capability to recognize returning ACK, NAK and
SYNC. When a SYNC is received the sender stops all output, purges its
outputbuffers and tries to get the resyncinfo.
(some smart programming to allow an unintended sync caused by
linenoise may make the protocol more stable. You may also test for
ack/nack directly after the SYNC because the ascvii blocknumer
garantees that a received ack/nak probably means a spurious sync. ).
As soon as the blocknumber is received the sender acks and resume
the sealink transfer at the given block.

NOTES
------

This only works if the receiver closes a partly recived file
properly, gives it the right name and sets the right time/date.
In the current dutchie 2.80 implementation it also
only works for files, not for mailpackets, but that is only a
question of implementation and choise.

IMPLEMENTATION
---------------

Currently only dutchie 2.80 implements this enhancement. testing
has shown that the protocol is very stable and works well.
Some code in turbo pascal follows to help those who want to
implement it.

1. The code used for transferring the wanted restart blocknumber
  to the sender. In real implementations this code will be shared
  by the filerequest stuff.

 function resyncok(blknum:integer):boolean;

Var
blockstring : string[5];
tries,
ch,
n            : Integer;

Begin
 str(blknum,blockstring);
 tries := 0;
 repeat
   tries := tries +1;
   if ((not Comm_Carrier(Comport)) or keyescape or ( tries >=8)) then
   begin
     If not Comm_Carrier(Comport) then Logit(3,1,'Lost Carrier') else
     If (tries>=8) then Logit(3,1,'Too much errors') else
     Logit(3,1,'Keyboard <esc>');
     dumerr := fileerr;
     resyncok := false;
     exit;
   end;
   Comm_purge_in(ComPort);
   ClearCrC;
   comm_transmit(comport,22);
   For n:= 1 to length(blockstring) do
   Begin
     Comm_transmit(Comport,Ord(blockstring[n]));
     UpdatCrc(ord(blockstring[n]));
   End;
   UpdatCrc(0);
   UpdatCrc(0);
   Comm_Transmit(Comport,$03);
   Comm_Transmit(ComPort,Lo(CrcAccum));
   Comm_Transmit(ComPort,Hi(CrcAccum));
   Comm_purge_in(comport);
   {wait for a 1 sec pause}
   {Wait until line dies}
   Repeat
     Ch := timed_read(ComPort, 10);
   Until (Ch = $FFFF);
   comm_transmit(comport,05);
   ch := timed_read(Comport,20);
 until (ch=ACK);
 resyncok := true;
end;


2. part of sender ack/nack logic to handshake
  above code

function getsyncblock(var c:integer):Boolean;
var t1 : real;
   n,
   Crclo,
   CrcHi,
   pl,
   code,
   ch : integer;
   temp1,
   temp : string64;
   label 100;


begin
 ReqName := '';
 getsyncblock := false;
 t1 := timerset(50);
 repeat
   ch := timed_read(comport,0);
   if ((ch > $1F) and (ch <$7F)) then ReqName := ReqName + Chr(ch);
   if ((ch = ack) or (ch = nak)) then
   begin
     c:= ch;
     goto 100;
   end;
   if not comm_carrier(Comport) then goto 100;
 until ((ch = $03) or timeup(t1));
 CrcLo := Timed_Read(Comport,10);
 CrcHi := Timed_Read(Comport,10);
 ClearCrc;
 For n := 1 to length(ReqName) do UpdatCrc(ord(reqName[n]));
 UpdatCrc(0);
 UpdatCrc(0);
 {now wait for enquiry (must be within 5 secs)}
 t1 := timerset(50);
 repeat
   ch := timed_read(comport,50);
 until ((ch = $05) or timeup(t1));

 If ((Lo(CrcAccum) = CrcLo) and (Hi(CrcAccum) = CrcHi)) then
 Begin
   val(reqname,outblk,pl);
   Comm_transmit(Comport,ACK);
   getsyncblock :=true;
 end
 else
   begin
   fixwindow;
   Writeln('           Bad Checksum');
   Comm_transmit(comport,Nak);
   end;
100:
end;




 Procedure AckChk;

   {  The Various ACK/NAK states are:
   0:   Ground state, ACK or NAK expected
   1:   ACK received
   2:   NAK received
   3:   ACK, bloknumber received
   4:   NAK, bloknumber received
   }

 Var
   c : Integer;
   label 100;

 Begin

   ackrep := false;
   c := timed_read(ComPort,0);
   While c <> $FFFF Do
   Begin
     If ((Ackst = 3) Or (Ackst = 4)) Then
     Begin
       Slide := 1;
       If (Rawblk = (c Xor $FF)) Then
       Begin
         Rawblk := Outblk-((Outblk-Rawblk) And $FF);
         If ((Rawblk >= 0) And (Rawblk <= Outblk) And (Rawblk > (Outblk-128))) Then
         Begin
           If (Ackst = 3) Then  {advance for an ACK}
           Begin
             If (Ackblk <= Rawblk) Then Ackblk := Rawblk;
             Slide := SeaWindow;
             ackseen := ackseen + 1;
             if (ackless and (ackseen > 10)) then
             begin
               ackless := false;
               fixwindow;
               writeln(#13,'- Overdrive disengaged                  ');
             end;
             fixwindow;
             Write(#13, '  ACK ', Rawblk:5, ' == ')
           End
           Else
           Begin
             If (Rawblk < 0) Then Outblk := 0 Else Outblk := Rawblk;
             If numnak < 4 then slide := seawindow else slide := 1;
             fixwindow;
             Write(#13, '  NAK ', Rawblk:5, ' == ');
           End;
           Ackrep := true;
         End;
       End;
       Ackst := 5;
     End;

       If ((Ackst = 1) Or (Ackst = 2)) Then
       Begin
         Rawblk := c;
         Ackst := Ackst+2
       End;

       If (Not(Slide = SeaWindow) Or (Ackst = 0)) Then
       Begin
         If (c = syn) then
         begin
           Write(#13, '  Resync received                          ',#13);
           if not getsyncblock(c) then
           begin
              if ((c = ack) or (c=nak)) then goto 100;
              numnak := 255;
               exit;
           end;
           ackblk := outblk-1;
           beginblk := outblk-1;
         end;
100:
         If (c = Ack) Then
         Begin
           If (Not(Slide = SeaWindow)) Then
           Begin
             Ackblk := Ackblk+1;
             fixwindow;
             Write(#13, '  ACK ', Ackblk:5, ' -- ');
             ackrep := true;
           End;
           Ackst := 1;
           NumNak := 0;
         End
         Else
         Begin
           If ((c = Crc) Or (c = Nak)) Then
           Begin
             If (Chktec > 1) Then
             Begin
               If (c = Nak) Then Chktec := 0 Else Chktec := 1;
               If (Modem Or Modem7) Then Ackblk := 0;
             End;
             Comm_purge_out(Comport);
             TimeWait(6);
             If Not(Slide = SeaWindow) Then
             Begin
               Outblk := Ackblk+1;
               fixwindow;
               Write(#13, '  NAK ', Ackblk+1:5, ' -- ');
               Ackrep := true;
             End;
             Ackst := 2;
             NumNak := NumNak+1;
             If BlkSnt > 0 Then Toterr := Toterr+1;
           End;
         End;
       End;

       If (Ackst = 5) Then Ackst := 0;
       c := timed_read(ComPort,0);
     End;
 End;


3. part of receiver logic
----------------------------

{we come here after successfully receiving block zero}

  If Sealink then
  begin
    Timestring := Seatime((((Buffer[8]*256.0)+Buffer[7])*256.0+Buffer[6])*256.0+Buffer[5]);
    ackless := false;
     If (Buffer[41]  <> 0) then
    begin
      writeln('- Overdrive engaged');
      ackless := true;
    end;
    If (Buffer[42]  <> 0) then
    begin
      writeln('- Recovery enabled');
      recovers := true;
    end;
  end;
  Assign(Afile, FileDir+filenm);
  Reset(Afile);
  If IOResult = 0 Then
  Begin
    if sealink and recovers then
    begin
     {find date/time}
      code := FindFirst(Filedir+filenm);
      If code = 0 Then
      begin
        {we have a duplicate ?}
        If file_name = filenm then
        begin
          {check timestamp}
          tstring[0] := #4;
          tstring[1] := Chr(dir.time[1]);
          tstring[2] := Chr(dir.time[2]);
          tstring[3] := Chr(dir.date[1]);
          tstring[4] := Chr(dir.date[2]);
          if tstring  = timestring then
          begin
            Blknum :=Trunc(file_size/128)+1;
            startblk := blknum-1;
            Str(blknum,blkstring);
            LogIt(3,1, 'Resynced from '+blkstring);
            if resyncok(blknum) then
            begin
              resyncflag := true;
              longseek(afile,(blknum-1)*128);
              truncate(afile);
            end
            else
            begin
              sealinkrx := false;
              goto 150;
            end;
          end;
        end;
      end;
    end;
    if not resyncflag then
    begin
      if not overwrite then
      begin
        filenm[1] := '$';
        LogIt(3,1, 'Renamed to '+filenm);
      end
      else Logit(3,1,'Overwrote old file !');
    end;
  End;


PLEASE COMPARE THESE TO THE ORIGINAL SEA DOCUMENTS ON SEALINK
(IN C)