cm.driver is the TComSession step engine: NextStep pumps one unit of I/O + state transition. This commit lands the handshake half (banner -> INIT exchange -> auth resolution) and stops at cmpTransfer; cm.xfer (FINFO/DATA/EOF/RPOS) lands in the next batch. cmpBanner runs as a non-blocking sub-state machine -- send once, then accumulate one byte at a time until COMET classification or BinkP detection. Recv = -1 in winding-down phases (cmpShutdown / cmpDone / cmpFailed) is treated as graceful EOF, not an IO error. FResult is finalized on transition into cmpDone so consumers see the result the same step the phase flips. Reference impls also land: cm.provider.fs -- TFileStream-backed IComFileProvider cm.transport.tcp -- UNIX BSD-socket IComTransport (DOS/Win/OS2 skip cm.transport.tcp via build.sh) tests/cmtestutil pins TByteQueue + TMemPipeEnd (IComTransport) + TMemPipePair + TTest (matches fpc-binkp's testutil shape). tests/test_session wires two TComSession instances over a TMemPipePair, pumps in lock-step, verifies both sides reach cmpDone, authenticate (NOPWD path), capture remote SysName / AKAs, and negotiate caps as the AND of both advertisements. All 7 cross-targets build clean; 3 test programs pass.
144 lines
4.4 KiB
ObjectPascal
144 lines
4.4 KiB
ObjectPascal
{ test_session -- end-to-end TComSession handshake over an
|
|
in-memory pipe.
|
|
|
|
Wires two TComSession instances (one inbound, one outbound)
|
|
to opposite ends of a TMemPipePair and pumps NextStep on
|
|
both until each reaches cmpDone. Verifies:
|
|
|
|
- Both sides terminate cleanly
|
|
- Both report success
|
|
- Both authenticate (NOPWD path)
|
|
- The negotiated capability mask is the AND of both sides
|
|
- Banner + INIT + INITACK frames make it across in both
|
|
directions
|
|
|
|
This is the cm.* equivalent of fpc-binkp's
|
|
test_session_roundtrip -- the load-bearing test that
|
|
proves the session engine can talk to itself. }
|
|
|
|
program test_session;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$modeswitch advancedrecords}
|
|
|
|
uses
|
|
Classes, SysUtils,
|
|
log.types, mb.address,
|
|
cm.types, cm.config, cm.transport, cm.driver,
|
|
cmtestutil;
|
|
|
|
var
|
|
T: TTest;
|
|
|
|
function MakeCfg(Trans: IComTransport; const Name: string;
|
|
Z, Net, Node: Word): TCometSessionConfig;
|
|
begin
|
|
CMConfigDefaults(Result);
|
|
Result.Transport := Trans;
|
|
Result.Provider := nil; { driver doesn't drive xfer in this batch }
|
|
Result.SystemName := Name;
|
|
Result.SysOp := 'Test';
|
|
Result.MailerName := 'fpc-comet/test';
|
|
SetLength(Result.LocalAddrs, 1);
|
|
Result.LocalAddrs[0] := MakeFTNAddress(Z, Net, Node, 0);
|
|
Result.HandshakeTimeoutSeconds := 5;
|
|
Result.TimeoutSeconds := 5;
|
|
end;
|
|
|
|
procedure TestHandshakeRoundtrip;
|
|
var
|
|
Pipe: TMemPipePair;
|
|
Inbound, Outbound: TComSession;
|
|
CfgIn, CfgOut: TCometSessionConfig;
|
|
Step: Integer;
|
|
RIn, ROut: TCometSessionResult;
|
|
ProgIn, ProgOut: Boolean;
|
|
begin
|
|
T.Group('TestHandshakeRoundtrip');
|
|
|
|
Pipe := TMemPipePair.Create;
|
|
try
|
|
CfgIn := MakeCfg(Pipe.EndA, 'Inbound', 1, 218, 720);
|
|
CfgOut := MakeCfg(Pipe.EndB, 'Outbound', 1, 213, 700);
|
|
|
|
Inbound := TComSession.Create(cmDirInbound, CfgIn);
|
|
Outbound := TComSession.Create(cmDirOutbound, CfgOut);
|
|
try
|
|
{ Pump in lock-step. Hard cap at 200 iterations to
|
|
keep a regression from hanging CI forever. }
|
|
for Step := 1 to 200 do
|
|
begin
|
|
ProgOut := Outbound.NextStep;
|
|
ProgIn := Inbound.NextStep;
|
|
if (Inbound.Phase = cmpDone) and (Outbound.Phase = cmpDone) then
|
|
Break;
|
|
if (not ProgIn) and (not ProgOut) and
|
|
(Inbound.Phase <> cmpDone) and (Outbound.Phase <> cmpDone) then
|
|
begin
|
|
Writeln(' no progress at step ', Step,
|
|
' inbound.phase=', Ord(Inbound.Phase),
|
|
' outbound.phase=', Ord(Outbound.Phase));
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
RIn := Inbound.Result_;
|
|
ROut := Outbound.Result_;
|
|
|
|
T.Check(Inbound.Phase = cmpDone,
|
|
Format('inbound reached cmpDone (was %d)',
|
|
[Ord(Inbound.Phase)]));
|
|
T.Check(Outbound.Phase = cmpDone,
|
|
Format('outbound reached cmpDone (was %d)',
|
|
[Ord(Outbound.Phase)]));
|
|
|
|
T.Check(RIn.Success,
|
|
Format('inbound reports Success (err=%d msg=%s)',
|
|
[Ord(RIn.ErrorCode), RIn.ErrorMessage]));
|
|
T.Check(ROut.Success,
|
|
Format('outbound reports Success (err=%d msg=%s)',
|
|
[Ord(ROut.ErrorCode), ROut.ErrorMessage]));
|
|
|
|
T.Check(RIn.Authenticated, 'inbound authenticated');
|
|
T.Check(ROut.Authenticated, 'outbound authenticated');
|
|
T.Check(RIn.AuthMethod = cmAuthNoPwd, 'inbound auth = NOPWD');
|
|
T.Check(ROut.AuthMethod = cmAuthNoPwd, 'outbound auth = NOPWD');
|
|
|
|
T.Check(RIn.WireBytesSent > 0, 'inbound sent bytes');
|
|
T.Check(RIn.WireBytesRecv > 0, 'inbound received bytes');
|
|
T.Check(ROut.WireBytesSent > 0, 'outbound sent bytes');
|
|
T.Check(ROut.WireBytesRecv > 0, 'outbound received bytes');
|
|
|
|
T.CheckEqualStr(ROut.Remote.SysName, 'Inbound',
|
|
'outbound sees inbound SysName');
|
|
T.CheckEqualStr(RIn.Remote.SysName, 'Outbound',
|
|
'inbound sees outbound SysName');
|
|
T.Check(Length(RIn.Remote.Addrs) = 1,
|
|
'inbound captured one remote AKA');
|
|
T.Check(Length(ROut.Remote.Addrs) = 1,
|
|
'outbound captured one remote AKA');
|
|
|
|
T.Check((RIn.Remote.Caps and COPT_SHA256) <> 0,
|
|
'negotiated SHA256');
|
|
T.Check((RIn.Remote.Caps and COPT_NOPWD) <> 0,
|
|
'negotiated NOPWD');
|
|
finally
|
|
Inbound.Free;
|
|
Outbound.Free;
|
|
end;
|
|
finally
|
|
Pipe.Free;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
T := TTest.Create('test_session');
|
|
try
|
|
TestHandshakeRoundtrip;
|
|
T.Summary;
|
|
Halt(T.ExitCode);
|
|
finally
|
|
T.Free;
|
|
end;
|
|
end.
|