{*******************************************************}
{                                                       }
{       nrCommLib Communication Library for Delphi      }
{       Kermit protocol implementation (VCL)            }
{       Copyright (c) 1997-2011 Roman Novgorodov        }
{       v9.27                                           }
{       last update: 14 Apr 2011                        }
{                                                       }
{       e-mail:noro@deepsoftware.com                    }
{       DeepSoftware                                    }
{       http://www.deepsoftware.com/                    }
{                                                       }
{*******************************************************}
{$I nrcommlib.inc}

unit nrkermit;

interface
uses windows, classes, nrftbase, nrdataproc;

Type

//  TnrKermitCrc = (crc6bit = 1, crc12bit = 2, crc16bit = 3);
  TnrKermitCrc = (crc6bit, crc12bit, crc16bit);

  TnrKermitHeader = packed record
    Mark:byte;
    Len:byte;
    Seq:byte;
    PacketType:byte;
  end;
  PnrKermitHeader = ^TnrKermitHeader;

  TnrKermitInitOptions = packed record
    MAXL:byte;
    TIME:byte;
    NPAD:byte;
    PADC:byte;
    EOL:byte;
    QCTL:byte;
    QBIN:byte;
    CHKT:byte;
    REPT:byte;
    CAPAS:byte;
  end;

  PnrKermitInitOptions = ^TnrKermitInitOptions;

  TnrKermit = class (TnrCustomFileTransfer)
  private
    fRcvPacketHeader:PnrKermitHeader;
    fRcvPacket:array[0..255] of byte;
    fSndPacketHeader:PnrKermitHeader;
    fSndPacket:array[0..255] of byte;
    fBuffer:array[0..255] of byte;
    fSndIdSequence:integer;
    fInitOptions:TnrKermitInitOptions;
    fPrevPacket : byte; // type of previous packet ...
    fPrevFilePos : integer;
    fFileIndex : integer;
    fCrcSession:TnrKermitCrc;
    fCrcDefault:TnrKermitCrc;
    fDelayedSkipACK:string;
    prevRcvId : byte;

    procedure HandlePacketLength(Packet: TnrDataPacket; chData: byte;
                                 var IsHandled: Boolean);
    procedure InitSessionStates(aNewState:TnrFileTransferState);
    procedure FreeSessionStates(aNewState:TnrFileTransferState);
    function GetAttributeData(chAttribute:byte):AnsiString;
    function EncodeFileSizeAttribute:AnsiString;
    function GetDefaultCrc: TnrKermitCrc;
    procedure SetDefaultCrc(const Value: TnrKermitCrc);

  protected
    procedure DoSendPacket(aType: byte; pData:PAnsiChar; aDataLen: integer);
    procedure DoHandlePacket(i:integer); override;
    procedure DoTimeoutError;             override;
    function CalcCrc(data: array of byte):integer;
    function CheckCrc(data: array of byte):boolean; virtual;
    procedure DoBadCrc;
    procedure DoAbort(aReason:string); override;

    // receive file methods ...
    procedure DoHandleInit;
    procedure DoHandleFile;
    procedure DoHandleFileAttributes;
    procedure DoHandleEof;
    procedure DoHandleBreak;
    procedure DoHandleData;
    procedure DoHandleError;

    // send file methods ...
    procedure DoHandleACK;
    procedure DoHandleNACK;

    procedure AnswerACK;
    procedure AnswerNACK;

    procedure AnswerInit;
    procedure AnswerFileHeader;
    procedure AnswerFileAttributes;
    procedure AnswerDataPacket(flNext:boolean);
    procedure AnswerEof(aDel:boolean);
    procedure AnswerBreak;
    procedure AnswerFatalError(aReason:string);

  public
    constructor Create(AOwner:TComponent); override;
    destructor Destroy; override;
    procedure Send(aFileName:string); override;
    procedure Reset; override;
    procedure SkipFile(aFileGroup, aDelete:boolean); override;
    property CrcSession :  TnrKermitCrc read fCrcSession;
    property Errors;
    property State;
    property TotalSize;
    property FileSize;
    property FilePosition;
    property FileName;

  published
    property Crc: TnrKermitCrc read GetDefaultCrc write SetDefaultCrc;
    property Active;
    property Log;
    property DataProcessor;
    property DefaultPath;
    property Timeout;
    property ErrorLimit;
    {$IFDEF NR_WITH_CONTROLS}
    property ProgressBar;
    {$ENDIF}
    property OnFileStart;
    property OnFileEnd;
    property OnProgress;
    property OnError;
    property OnChangeState;
    property OnFatalError;

  end;

Var
  BadCrcOut:boolean = false;
  BadCrcIn:boolean = false;


implementation

uses nrclasses, sysutils;


Const

  // Kermit Packet Types ...
  KT_DATA_PACKET            = Byte('D'); // Data packet
  KT_ACK                    = Byte('Y'); // Acknowledge (ACK)
  KT_NACK                   = Byte('N'); // Negative acknowledge (NAK)
  KT_SEND_INIT              = Byte('S'); // Send initiate (exchange parameters)
  KT_BREAK                  = Byte('B'); // Break transmission (EOT)
  KT_FILE_HEADER            = Byte('F'); // File header
  KT_EOF                    = Byte('Z'); // End of file (EOF)
  KT_ERROR                  = Byte('E'); // Error
  KT_FILE_ATTRIBUTES        = Byte('A'); // File Attributes ...

  // File attribute markers ...
  KT_FA_LENGTH              = Byte('!'); // length of file in Kb ...
  KT_FA_DATE                = Byte('#'); // creation date of file ...

  // The synchronization character that marks the beginning of the packet
  K_DEFAULT_MARK            = 01;

  MAX_PACKET_LENGTH         = 94;
  MAX_ATTRIBUTE_LENGTH      = 94;

  CS_ERROR_REMOTE           = 'Remote end reports fatal error: %s';
//  CS_ERROR_TOOMANY          = 'Too many CRC errors.';


{ TnrKermit }

constructor TnrKermit.Create(AOwner: TComponent);
begin
  inherited;
  fCrcSession := crc6bit;
  sLogPrefix := 'nrKermit: ';
  fRcvPacketHeader := @fRcvPacket[0];
  fSndPacketHeader := @fSndPacket[0];
  fSndPacketHeader.Mark := Byte(K_DEFAULT_MARK);
  fState := fsListen;
  with DataPackets.Add do begin  // Kermit packet ...
    Caption := 'Common Kermit Packet';
    Active := True;
    PacketBegin := AnsiChar(K_DEFAULT_MARK);
    OnPacketLength := HandlePacketLength; // handler detects a length of packet
  end;
  Timeout := 5000;
end;

destructor TnrKermit.Destroy;
begin
  inherited;
end;

procedure TnrKermit.Reset;
begin
  FreeSessionStates(fsListen);
//  fState := fsListen;
//  fSndIdSequence := 0;
  inherited Reset;
end;

procedure TnrKermit.InitSessionStates(aNewState:TnrFileTransferState);
begin
  FreeSessionStates(aNewState);
  flSkipFurtherProcessing := True;
end;

procedure TnrKermit.FreeSessionStates(aNewState:TnrFileTransferState);
begin
  fSndIdSequence := 0;
  fPrevPacket := 0;
  fPrevFilePos := 0;
  fFileIndex := 0;
  fErrors := 0;
  flSkipFurtherProcessing := False;
//  if aNewState = fsListen
//    then fCrcSession := crc6bit;
  DoChangeState(aNewState);
  ResetTimeout(0);
end;

procedure TnrKermit.HandlePacketLength(Packet: TnrDataPacket; chData: byte;
  var IsHandled: Boolean);
begin
  // detect variable datapacket length and set it into needed property ...
  Packet.PacketLength := byte(chData) - 32 + Packet.DataLength;
  IsHandled := True;
end;

procedure TnrKermit.Send(aFileName: string);
begin
  inherited Send(aFileName);
  if fFiles.Count > 0 then AnswerInit;
end;

procedure TnrKermit.DoAbort(aReason:string);
//var ch : AnsiChar;
begin
  if State <> fsListen then AnswerFatalError(aReason);
  {
  if State = fsSend then AnswerBreak
  else
  if State = fsReceive then begin
    ch := 'X';
    DoSendPacket(KT_ACK, @ch, 1);
    DoHandleBreak;
  end;
  }
  inherited DoAbort(aReason);
  DoCloseFile;
end;

function TnrKermit.CalcCrc(data: array of byte): integer;
var i, q, c, len :integer;
begin
  Result := 0;
  len := data[1] - 32 - Integer(fCrcSession);
  if fCrcSession <> crc16bit then begin
    for i := 1 to len
      do Result := Result + data[i];
    if fCrcSession = crc6bit
      then Result := (Result + ((Result and 192) shr 6)) and 63
      else Result := Result and $FFF;
  end else begin
    for i := 1 to len do begin
      c := data[i];// and $7F;
      q := (Result xor c) and 15;
      Result := (Result shr 4) xor (q * 4225);
      q := (Result xor (c shr 4)) and 015;
      Result := (Result shr 4) xor (q * 4225);
    end;
    Result := Result and $FFFF;
  end;
end;

procedure TnrKermit.DoSendPacket(aType: byte; pData:PAnsiChar; aDataLen: integer);
var i, len_packet, len_crc, crc:integer;
//    s : string;
begin
  len_crc := Integer(fCrcSession) + 1;
  len_packet := SizeOf(TnrKermitHeader) - 2 + aDataLen + len_crc;

  // fill packet header - first four bytes of packet array ...
  fSndPacketHeader.Mark := Byte(K_DEFAULT_MARK);
  fSndPacketHeader.Len := 32 + len_packet;
  fSndPacketHeader.Seq := (fSndIdSequence mod 64) + 32;
  fSndPacketHeader.PacketType := aType;

  // copy data source into packet array ....
  for i := 0 to aDataLen - 1 do
    fSndPacket[i + 4] := Byte(pData[i]);

  crc := CalcCrc(fSndPacket);

  if BadCrcOut then begin
    BadCrcOut := False;
    Inc(crc);
  end;


  // fill check sum fields ...
  for i := 0 to len_crc - 1
    do fSndPacket[len_packet + 1 - i] := 32 + (crc shr (i * 6)) and 63;

  fSndPacket[len_packet + 2] := 13;

  fPrevPacket := aType;

  DoDataSend(@fSndPacket[0], len_packet + 3);
  Inc(fSndIdSequence);
  ResetTimeout(Timeout);

end;

function TnrKermit.CheckCrc(data: array of byte): boolean;
var i, crc, len:integer;
begin
  Result := True;
  len := data[1] - 32 + 1;
  crc := CalcCrc(data);

  if BadCrcIn then begin
    BadCrcIn := False;
    Inc(crc);
  end;

  for i := 0 to Integer(fCrcSession) do
    if data[len - i] <> (32 + (crc shr (i * 6)) and 63)
      then begin
        Result := False;
        break;
      end;
end;

procedure TnrKermit.DoHandlePacket(i: integer);
var len:integer;
 //   s:string;
begin
  inherited DoHandlePacket(i);

  // packet is received, now copy it into our kermit structure ...

  len := DataPackets[0].DataLength;
  CopyMemory(@fRcvPacket[0], DataPackets[0].DataPtr, len);

{  s := '';
  for j := 0 to len - 1 do begin
    s := s + Format('%X ', [fRcvPacket[j]]);
    if ((j mod 32) = 0) or (j >= (len - 1)) then begin
      OutputDebugString(PChar('incoming DATA: ' + s ));
      s := '';
    end;
  end;}

  if fRcvPacketHeader.PacketType = KT_SEND_INIT
    then fCrcSession := crc6bit;

  if not CheckCrc(fRcvPacket) then begin
    fRcvPacketHeader.Seq := fRcvPacketHeader.Seq - 32;
    DoBadCrc;
    exit;
  end;
//    else OutputDebugString(PChar('OK CRC. '  + IntToStr(Integer(CrcSession)) ));

  // convert numbers from printble view ...
  fRcvPacketHeader.Len := fRcvPacketHeader.Len - 32;
  fRcvPacketHeader.Seq := fRcvPacketHeader.Seq - 32;


  if fRcvPacketHeader.PacketType = KT_ERROR then begin
    if State <> fsListen then DoHandleError;
    exit;
  end;

  if fRcvPacketHeader.PacketType = KT_BREAK then begin
    AnswerACK;     // confirm packet
    DoHandleBreak();    
    exit;
  end;


  //OutputDebugString(PChar('kermit packet!!! = ' + fRcvPacketHeader.PacketType));

  case State of
    fsListen:
      if( fRcvPacketHeader.PacketType = KT_SEND_INIT) then begin
        DoHandleInit();
        AnswerACK;
      end;
    fsReceive:
      begin
        // packets from sender ...
        case fRcvPacketHeader.PacketType of
          //KT_SEND_INIT:        DoHandleInit();
          KT_FILE_HEADER:      DoHandleFile();
          KT_FILE_ATTRIBUTES : DoHandleFileAttributes();
          KT_DATA_PACKET:      DoHandleData();
          KT_EOF:              DoHandleEof();
//          KT_BREAK:            DoHandleBreak();
        end;
        AnswerACK;     // confirm packet
      end;
    fsSend:
      // packets from receiver ...
      case fRcvPacketHeader.PacketType of
        KT_ACK:              DoHandleACK;
        KT_NACK:             DoHandleNACK;
      end;
  end;

  prevRcvId := fRcvPacketHeader^.Seq;

end;

procedure TnrKermit.DoBadCrc;
begin
  if DoError() then begin
    Logging(dlDetail, 'Bad check sum! ' + IntToStr(Integer(CrcSession)));
    if State = fsReceive then AnswerNACK();
  end;
end;

procedure TnrKermit.DoTimeoutError;
begin
  if State = fsListen then begin
    ResetTimeout(0);
    exit;
  end;
  inherited DoTimeoutError;
  if DoError() then begin
    Logging(dlDetail,'Timeout!');
    AnswerNACK();
  end;
end;

procedure TnrKermit.DoHandleInit;
var i : integer;
    s :string;
begin

//  if fState <> fsListen then begin
//  end;

  fInitOptions := PnrKermitInitOptions(@fRcvPacket[SizeOf(TnrKermitHeader)])^;

  s := '';
  for i := 4 to fRcvPacketHeader.Len - 2
    do s := s + IntToStr(fRcvPacket[i]) + '/' + IntToHex(fRcvPacket[i], 2) + ' ';

  Logging(dlDebug,'INIT [S] Packet: ' + Char(fInitOptions.QBIN) + ' ' + Char(fInitOptions.CHKT) + ' = ' + s);

  fCrcSession := TnrKermitCrc(Integer(fInitOptions.CHKT - $31));

  InitSessionStates(fsReceive);
end;

procedure TnrKermit.DoHandleFile;
var sFName:PAnsiChar;
//    len :integer;
begin
//  len := fRcvPacketHeader^.Len - Integer(fCrcSession);
  fRcvPacket[fRcvPacketHeader^.Len - Integer(fCrcSession) + 1] := 0;
  sFName := @fRcvPacket[SizeOf(TnrKermitHeader)];
  Logging(dlDebug,'FILE HEADER [F] Packet! ' + String(sFName));
  DoOpenFile(String(sFName), fmCreate);
end;

function TnrKermit.GetAttributeData(chAttribute:byte):AnsiString;
var i, len:integer;
begin
  Result := '';
  i := SizeOf(TnrKermitHeader);
  while i < (fRcvPacketHeader^.Len + 1) do begin
    len := fRcvPacket[i + 1] - 32;
    if len <= 0 then break;
    if fRcvPacket[i] = Byte(chAttribute) then begin
      SetLength(Result,len);
      CopyMemory(PAnsiChar(Result),@fRcvPacket[i + 2],len);
      break;
    end;
    Inc(i,len + 2);
  end;
end;

procedure TnrKermit.DoHandleFileAttributes;
begin
  fFileSize := 1024 * StrToIntDef(String(GetAttributeData(KT_FA_LENGTH)), 0) + 1;
  SetTotalSize(fFileSize);
//  fTotalSize := fFileSize;
  Logging(dlDebug,'FILE ATTRIBUTES [A] Packet! ' + IntToStr(fFileSize)
                   + ' ' + String(GetAttributeData(KT_FA_DATE)));
end;

procedure TnrKermit.DoHandleEof;
var fn : string;
    s : string;
begin
  fn := FileName;
  s := 'EOF [Z] Packet. Close File:  ' + fn;
  DoCloseFile;
  // if Z packet contains D in data field, remote end requests delete file ...
  if (fRcvPacketHeader.Len > 3)
     and (fRcvPacket[4] = Byte('D'))
     and FileExists(fn)
    then begin
      s := s + ' with deletion!';
      DeleteFile(fn);
    end;
  Logging(dlDebug, s);
end;

procedure TnrKermit.DoHandleBreak;
begin
//  DoCloseFile;
  FreeSessionStates(fsListen);
  Logging(dlSimple,'BREAK [B] Close Session Packet! ');
end;

procedure TnrKermit.DoHandleData;
var i, len :integer;
    bPrev : byte;
    s:string;
begin

  // check if this data is repeat from sender after our WRONG ACK ...
  if prevRcvId = fRcvPacketHeader^.Seq then exit;

  len := fRcvPacketHeader^.Len - Integer(fCrcSession);
  bPrev := 0;
  s := '';

  i := SizeOf(TnrKermitHeader);

  while i <= len do begin
    bPrev := fRcvPacket[i];
    if bPrev = fInitOptions.QCTL then begin
      inc(i);
      bPrev := fRcvPacket[i];
      if (bPrev <> fInitOptions.QCTL) and (fRcvPacket[i] <> $A3) then bPrev := bPrev xor 64;
    end;

    try
      if State <> fsReceive then break;
      fFile.Write(bPrev, 1);
    except
      break;
    end;

    inc(i);

  end;

  DoProgress;
  Logging(dlDebug,'DATA [D] Packet! ');

end;

procedure TnrKermit.DoHandleACK;
begin

  Logging(dlDebug,'ACK [Y] Packet!');

  case fPrevPacket of
    KT_SEND_INIT:        begin
      fInitOptions := PnrKermitInitOptions(@fRcvPacket[SizeOf(TnrKermitHeader)])^;
      fCrcSession := TnrKermitCrc(Integer(fInitOptions.CHKT - $31));
      AnswerFileHeader();
    end;
    KT_FILE_HEADER:      AnswerFileAttributes();
    KT_FILE_ATTRIBUTES : AnswerDataPacket(True);

    KT_DATA_PACKET:      begin
      if (fRcvPacketHeader.Len > 3) and ((AnsiChar(fRcvPacket[4]) = 'X') or (AnsiChar(fRcvPacket[4]) = 'Z'))
        then SkipFile(AnsiChar(fRcvPacket[4]) = 'Z', True)
        else AnswerDataPacket(True);
    end;
    KT_EOF:              begin
      if fFileIndex < fFiles.Count
        then AnswerFileHeader()
        else AnswerBreak();
    end;
    KT_BREAK:            DoHandleBreak();
  end;
end;

procedure TnrKermit.DoHandleNACK;
begin
  Logging(dlDebug,'NACK [N] Packet! ');
  if State = fsListen then exit;
  case fPrevPacket of
    KT_SEND_INIT:        AnswerInit;
    KT_FILE_HEADER:      AnswerFileHeader();
    KT_FILE_ATTRIBUTES : AnswerFileAttributes();
    KT_DATA_PACKET:      AnswerDataPacket(False);
    KT_EOF:              AnswerEof(False);
//    KT_BREAK:            DoHandleBreak();
  end;

end;

procedure TnrKermit.AnswerACK();
var saveType: TnrKermitCrc;
    pData:Pointer;
    len:integer;
begin
  fSndIdSequence := fRcvPacketHeader.Seq;
  if fRcvPacketHeader.PacketType = KT_SEND_INIT
    then begin
      // ACK to init packet in simple CRC type always
      saveType := fCrcSession;
      fCrcSession := crc6bit;
      fInitOptions.REPT := $20; // does not support repeating
      DoSendPacket(KT_ACK, @fInitOptions, SizeOf(fInitOptions));
      fCrcSession := saveType;
    end
  else begin
    pData := nil;
    len := 0;
    if (fRcvPacketHeader.PacketType = KT_DATA_PACKET) and (Length(fDelayedSkipACK) = 1)
      then begin
        pData := PAnsiChar(@fDelayedSkipACK[1]);
        len := 1;
      end;
    DoSendPacket(KT_ACK, pData, len);
    fDelayedSkipACK := '';
  end;
  Logging(dlDebug, 'Answer ACK ' + IntToStr(fSndPacketHeader.Seq - 32) + ' = ' + IntToStr(fRcvPacketHeader.Seq));
end;

procedure TnrKermit.AnswerNACK;
begin
  fSndIdSequence := fRcvPacketHeader.Seq;
  DoSendPacket(KT_NACK, nil, 0 );
  Logging(dlDebug, 'Answer NACK '  + IntToStr(fSndPacketHeader.Seq - 32) + ' = ' + IntToStr(fRcvPacketHeader.Seq));
end;

procedure TnrKermit.AnswerInit;
var tmSec:integer;
begin
  fCrcSession := crc6bit;

  tmSec := Timeout div 1000;

  if tmSec < 1 then tmSec := 5
  else
  if tmSec > 60 then tmSec := 60;

  with fInitOptions do begin
    MAXL := MAX_PACKET_LENGTH + 32;
    TIME := tmSec + 32;
    NPAD := 32;
    PADC := 64;
    EOL  := 13 + 32;
    QCTL := Byte('#');
    QBIN := Byte('Y');
    CHKT := Byte(fCrcDefault) + $31;
    REPT := $20;    // does not support repeating
    CAPAS := $28;
  end;

  InitSessionStates(fsSend);

  DoSendPacket(KT_SEND_INIT, @fInitOptions, SizeOf(fInitOptions));
  Logging(dlDebug, 'Answer Init');

end;

procedure TnrKermit.AnswerFileHeader;
var s:AnsiString;
    len:integer;
begin
  DoOpenFile(fFiles[fFileIndex],fmOpenRead);
  SetTotalSize(Integer(fFiles.Objects[fFileIndex]));
  s := AnsiString(ExtractFileName(FileName));
  len := Length(s);
  if len > 90 then begin
    s := Copy(s, 1, 90);
    len := 90;
  end;
  CopyMemory(@fSndPacket[SizeOf(TnrKermitHeader)],@s[1], len);
  DoSendPacket(KT_FILE_HEADER, @s[1], len);
  Logging(dlDebug, 'Answer File');
end;

function TnrKermit.EncodeFileSizeAttribute: AnsiString;
var sSize:AnsiString;
    len:integer;
begin
  sSize := AnsiString(IntToStr((TotalSize div 1024) + 1));
  len := Length(sSize);
  if len > MAX_ATTRIBUTE_LENGTH then begin
    len := MAX_ATTRIBUTE_LENGTH;
    FillChar(sSize, len, '9');
  end;
  len := len + 32;
  Result := AnsiChar(KT_FA_LENGTH) + AnsiChar(Byte(len)) + sSize;
end;

procedure TnrKermit.AnswerFileAttributes;
var s:AnsiString;
begin
  Logging(dlDebug, 'Answer File Atributes');
  // adds file size attribute ...
  s := EncodeFileSizeAttribute;
  DoSendPacket(KT_FILE_ATTRIBUTES, @s[1], Length(s));
end;

procedure TnrKermit.AnswerDataPacket(flNext: boolean);
var len, max_len :integer;
    b : byte;
begin
  if not flNext then begin
    fFile.Position := fPrevFilePos;
    //OutputDebugString('repeat');
    Dec(fSndIdSequence);
  end;

  len := 0;

  max_len := fInitOptions.MAXL - 32 - Integer(fCrcSession) - 1 -  2;

  fPrevFilePos := fFile.Position;

  while (fFile.Position < fFile.Size) and (len < max_len) do begin
    try
      fFile.Read(b, 1);
    except
      len := 0;
      break;
    end;
    if (b < $20) or (b = fInitOptions.QCTL) { or (b = $7E) or (b = $FE) or (b = $A3)}
      or (b = $FF) or (b = $7F)
      then begin
      if len = (max_len - 1) then begin
        fFile.Position := fFile.Position - 1;
        break;
      end;
      fBuffer[len] := fInitOptions.QCTL;//Byte('#');
      inc(len);
      if (b <> fInitOptions.QCTL) //and (b <> $7E) and (b <> $FE) and (b <> $A3) //and (b <> $FF) and (b <> $7F)
        then b := b xor 64;

    end;

    if b = $FF then b := $7F
    else
    if b = $7F then b := $FF;

    fBuffer[len] := b;
    inc(len);

  end;

  if len > 0 then begin
    DoSendPacket(KT_DATA_PACKET, @fBuffer[0], len);
    DoProgress;
  end
    else AnswerEof(False);

end;

procedure TnrKermit.AnswerEof(aDel:boolean);
begin
  DoCloseFile;
  Inc(fFileIndex);
  if aDel
    then DoSendPacket(KT_EOF, 'D', 1)
    else DoSendPacket(KT_EOF, nil, 0);
  Logging(dlDebug,'Answer EOF with deletion = ' + IntToStr(Integer(aDel)));
end;

procedure TnrKermit.AnswerBreak;
begin
  DoSendPacket(KT_BREAK, nil, 0);
  Logging(dlDebug,'Answer BREAK');
  FreeSessionStates(fsListen);
end;

function TnrKermit.GetDefaultCrc: TnrKermitCrc;
begin
  Result := fCrcDefault;
end;

procedure TnrKermit.SetDefaultCrc(const Value: TnrKermitCrc);
begin
  fCrcDefault := Value;
end;

procedure TnrKermit.DoHandleError;
var s:string;
    len:integer;
begin
  len := fRcvPacketHeader^.Len - Integer(fCrcSession) - 2;
  if len > 0 then begin
    SetLength(s, len);
    CopyMemory(@s[1], @fRcvPacket[4], len);
  end else s := '';

  FreeSessionStates(fsListen);
  DoAbort(Format(CS_ERROR_REMOTE, [s]));

end;

procedure TnrKermit.AnswerFatalError(aReason: string);
begin
  DoSendPacket(KT_ERROR, PAnsiChar(AnsiString(aReason)), Length(aReason));
  Logging(dlDebug,'Answer ERROR:' + aReason);
end;

procedure TnrKermit.SkipFile(aFileGroup, aDelete: boolean);
begin

  inherited SkipFile(aFileGroup, aDelete); // logging ...

  if State = fsSend then begin
    AnswerEof(aDelete);
    if aFileGroup then fFiles.Clear;
  end
  else
  if State = fsReceive then begin
    // set param of ACK reply. It will be used later, after DATA_PACKET received only ...
    if aFileGroup
      then fDelayedSkipACK := 'Z'
      else fDelayedSkipACK := 'X';
  end;
end;

end.

