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)