|
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 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);
}

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. |