Implementing Kermit Client "Server" Commands in Async Professional
Async Professional supports a number of different file transfer protocols; actually all of the common ones. The one we shall talk about here is the Kermit protocol; for full details, you can visit Columbia University's Kermit Project home page.

One particularly interesting feature of full Kermit implementations is their ability to go into a "Server" mode whereby they accept commands from clients. We've occasionally been asked whether APRO supports Kermit server commands, and previously our answer has been no.

But now, after a bit of research into the topic, we have discovered that implementing the basic command set for clients is sufficiently simple that we could do it as a Tech Tip article. First, a little background.

The Kermit specification provides for a the possibility of a number of commands, though the precise definition of most of them is "implementation dependent". However, the specification lists three commands as required; these are also the most generally useful:

 

Get:

Requests a named file from the server end, initiating a server download operation.

Send:

Posts a named file to the server end, initiating a server upload operation.

Finish (or Bye):

Tells the server that you're done transferring files and can exit server mode.

Under a normal Kermit dial-up session, you'd follow the standard procedures to log into the remote system and give it whatever commands/procedures it expects to go into Kermit Server mode (in most command line/console implementations of Kermit you do this simply by typing "server" at the Kermit command prompt). Then all you have to do is switch back to your local implementation and start sending Server commands.

So, how do I get started?
"So, how do I do this?", you may well ask.

The short answer is "Simple: the Kermit protocol is defined in terms of 'Packets' which are discrete chunks of data conforming to a certain internal layout. Kermit server commands are implemented as specialized forms of Kermit packets. Sending one of these special packets to a remote Kermit in server mode initiates the command."

Which begs the question of more precisely how to do it specifically with APRO, which is what we're here to tell you.

Without going into a lot of details on precisely why this is set up this way (take it on faith that this is what Kermit wants and there are good reasons for it; you can obtain the Kermit docs from Columbia if you're really interested) the following code will generate properly formatted Kermit "Packets":

 

const
  K_MARK_CH  = #1;   { Kermit start of packet }

function kToChar(Ch: Char): Char;
{ convert char range #0..#94 to ' '..'~' }
begin
  Result := Chr(Ord(Ch) + $20);
end;

function kUnChar(Ch: Char): Char;
{ convert from ' ' .. '~' to #0..#94 }
begin
  Result := Chr(Ord(Ch) - $20);
end;

function kCtl(Ch: Char): Char;
{ "(un)controllify" ASCII #0..#31 & #127 }
begin
  Result := Chr(Ord(Ch) xor $40);
end;

function SumChars(const S: string): LongInt;
var
  i: LongInt;
begin
  Result := 0;
  for i := 1 to Length(S) do
    Result := Result + Ord(S[i]);
end;

function kChkl(const Packet: string): Integer;
var
  Sum: LongInt;
begin
  Sum := SumChars(Packet);
  Result := (((Sum and 192) shr 6) + Sum) and 63;
end;

function MakeKermitPacket(PacketType: Char; 
         Seq: Integer; const Data: string): string;
var
  Packet: string;
begin
  Packet :=
    kToChar(Chr(Length(Data) + 3)) +  { Packet Length }
    kToChar(Chr(Seq)) +               { Packet Sequence }
    PacketType +                      { Packet Type }
    Data;                             { Data }

  Result :=
    K_MARK_CH +                       { Kermit start packet }
    Packet +
    kToChar(Chr(kChkl(Packet))) +     { Checksum }
    #13;
end;

Here's the same code implemented in C++Builder:

#define  K_MARK_CH  (char)0x01   // Kermit start of packet

char kToChar(char Ch)
// convert char range #0..#94 to ' '..'~'
{
  return (char)(Ch + 0x20);
}

char kUnChar(char Ch)
// convert from ' ' .. '~' to #0..#94
{
  return (char)(Ch - 0x20);
}

char kCtl(char Ch)
// "(un)controllify" ASCII #0..#31 & #127
{
  return (char)(Ch ^ 0x40);
}

long SumChars(String S)
{
  long i, Sum;
  Sum = 0;
  for (i = 1; i <= S.Length(); i++)
  {
    Sum = Sum + S[i];
  }
  return Sum;
}

int kChkl(String Packet)
{
  long Sum = SumChars(Packet);
  return (((Sum & 192) >> 6) + Sum) & 63;
}

String MakeKermitPacket(char PacketType, int Seq, String Data)
{
  String Packet =
    (String)kToChar(Data.Length() + 3) +      // Packet Length
    (String)kToChar(Seq) +                    // Packet Sequence
    PacketType +                              // Packet Type
    Data;                                     // Data
  return
    (String)K_MARK_CH +                       // Kermit start packet
    Packet +
    (String)kToChar((kChkl(Packet))) +
    (char)(0x0D);
}

Putting it together
The Kermit specification for the three previously mentioned interesting Server commands says something like this:

 

Get:

Send an "R" packet with 0 sequence and the name of the requested file(s)* in the Data field.

Send:

Send a "S" packet with 0 sequence and an empty Data field (the file name(s) is provided in the context of the actual transmission).

Finish:

Send a "G" packet with 0 sequence and "FINISH" in the Data field.

 

*The file name may contain wildcards so that multiple files are downloaded and/or directory/folder paths to indicate files in places other than the default; but these generally must conform to whatever the wildcard and/or path syntax is on the host system, which may not be the same as what you're familiar with on PC systems.

Assuming you've got a project created with properly configured ApdComPort and ApdProtocol components, and a TEdit to contain the filename of interest, we can now translate all this into form methods you can add to your project:

procedure TForm1.KermitGet;
begin
  ApdComPort1.OutPut := MakeKermitPacket('R', 0, Edit1.Text);
  DelayTicks(9, true);
  ApdProtocol1.StartReceive;
end;

procedure TForm1.KermitSend;
begin
  ApdComPort1.Output := MakeKermitPacket('S', 0, '');
  DelayTicks(9, true);
  ApdProtocol1.FileMask := Edit1.Text;
  ApdProtocol1.StartTransmit;
end;

procedure TForm1.KermitFinish;
begin
  ApdComPort1.Output := MakeKermitPacket('G', 0, 'FINISH');
  DelayTicks(9, true);
end;

Again, here's the same code implemented in C++Builder:

void __fastcall TForm1::KermitGet()
{
  String S = MakeKermitPacket('R', 0, Edit1->Text);
  ApdComPort1->Output = S;
  ApdProtocol1->StartReceive();
}

void __fastcall TForm1::KermitSend()
{
  String S = MakeKermitPacket('S', 0, "");
  ApdComPort1->Output = S;
  DelayTicks(9, TRUE);
  ApdProtocol1->FileMask = Edit1->Text;
  ApdProtocol1->StartTransmit();
}

void __fastcall TForm1::KermitFinish()
{
  String S = MakeKermitPacket('G', 0, "FINISH");
  ApdComPort1->Output = S;
  DelayTicks(9,TRUE);
}

Then you can call these methods from appropriate places in your project such as TButton OnClick event handlers:

procedure TForm1.Button1Click(Sender: TObject);
begin
  KermitGet;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  KermitSend;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  KermitFinish;
end;

Here's the same code implemented in C++Builder:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  KermitGet();
}

void __fastcall TForm1::Button2Click(TObject *Sender)
{
  KermitSend();
}

void __fastcall TForm1::Button3Click(TObject *Sender)
{
  KermitFinish();
}

Now, after connecting to a remote site and placing their Kermit into server mode, using these methods should initiate the requested Server commands.

This site is not affiliated, endorsed, or otherwise associated with the entity formerly known as TurboPower Software. The owners and maintainers of Aprozilla.com were merely meager employees of the aforementioned organization, providing this site out of the pure goodness of their collective hearts. SourceForge.net Logo

Last updated: July 22, 2003.