Tridora-CPU/progs/xfer.pas
2024-09-19 14:12:22 +02:00

431 lines
7.6 KiB
ObjectPascal

(* Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details *)
program xfer;
const CksumPattern = $AFFECAFE;
SOH = #1;
STX = #2;
EOT = #4;
ENQ = #5;
ACK = #6;
BEL = #7;
NAK = #21;
TimeoutTicks = 200;
var blockNo:integer;
buf:^string;
invalid:boolean;
cmd:char;
filename:string;
size:integer;
done:boolean;
xferFile:file;
function calcChksum(last,this:integer):integer;
begin
calcChksum := ((last + this) xor CksumPattern) shl 1;
end;
function readword:integer;
var b3,b2,b1,b0:char;
begin
b3 := conin;
b2 := conin;
b1 := conin;
b0 := conin;
readword := (ord(b3) shl 24) or
(ord(b2) shl 16) or
(ord(b1) shl 8) or
ord(b0);
end;
procedure serReadBlock(var success:boolean);
var w0,w1,w2,w3,w4,w5,w6,w7,w8:integer;
chksum:integer;
s:integer;
procedure writeByte(b:char);
begin
if size <> 0 then
begin
write(xferFile,b);
size := size - 1;
end;
end;
procedure appendWordToFile(w:integer);
var b3,b2,b1,b0:char;
begin
b0 := chr(w and 255);
w := w shr 8;
b1 := chr(w and 255);
w := w shr 8;
b2 := chr(w and 255);
w := w shr 8;
b3 := chr(w);
writeByte(b3);
writeByte(b2);
writeByte(b1);
writeByte(b0);
end;
procedure calcChksum(d:integer);
begin
chksum := ((chksum + d) xor CksumPattern) shl 1;
end;
begin
chksum := 0;
w0 := readword;
w1 := readword;
w2 := readword;
w3 := readword;
w4 := readword;
w5 := readword;
w6 := readword;
w7 := readword;
s := readword;
calcChksum(w0);
calcChksum(w1);
calcChksum(w2);
calcChksum(w3);
calcChksum(w4);
calcChksum(w5);
calcChksum(w6);
calcChksum(w7);
if s <> chksum then
begin
success := false;
write(NAK);
{ writeln('invalid chksum ', s, ' ', chksum); }
end
else
begin
success := true;
appendWordToFile(w0);
appendWordToFile(w1);
appendWordToFile(w2);
appendWordToFile(w3);
appendWordToFile(w4);
appendWordToFile(w5);
appendWordToFile(w6);
appendWordToFile(w7);
blockNo := blockNo + 1;
write(ACK);
end;
end;
procedure waitForByte(var byteReceived:char; var timeoutReached:boolean);
var ticks:integer;
done:boolean;
begin
timeoutReached := true;
ticks := getticks;
done := false;
repeat
if conavail then
begin
done := true;
timeoutReached := false;
byteReceived := conin;
end;
until done or (getticks > ticks + TimeoutTicks);
end;
procedure waitForHeader(var invalid:boolean);
var done:boolean;
timedOut:boolean;
ch:char;
begin
waitForByte(ch, timedOut);
invalid := (ch <> STX) or timedOut;
end;
procedure receiveHeader(var invalid:boolean);
var ch:char;
timedOut:boolean;
w:integer;
cksum:integer;
begin
{ send protocol version, then wait for size header }
write('1');
waitForByte(ch, timedOut);
if timedOut or (ch <> SOH) then
begin
invalid := true;
exit;
end;
cksum := 0;
w := readword;
cksum := readword;
if w <> (not cksum) then
begin
write(NAK);
w := 0;
writeln('h chksum error');
end
else
write(ACK);
if w > 0 then
begin
size := w;
waitForHeader(invalid);
end
else
invalid := true;
end;
procedure receiveFile;
var ch:char;
invalid, timedOut:boolean;
ok:boolean;
done:boolean;
errorCount:integer;
begin
if length(filename) = 0 then
begin
writeln('Filename not set.');
exit;
end;
errorCount := 0;
waitForByte(ch, timedOut);
if timedOut then
begin
writeln('Timeout waiting for transmission start (ENQ or STX).');
exit;
end;
if ch = ENQ then
receiveHeader(invalid)
else
if ch = STX then
begin
size := -1;
invalid := false;
end
else
invalid := true;
if not invalid then
begin
open(xferFile, filename, ModeOverwrite);
done := false;
repeat
serReadBlock(ok);
if not ok then errorCount := errorCount + 1;
waitForByte(ch, timedOut);
if timedOut then
writeln('Timeout waiting for next block (STX)');
if ch = EOT then
done := true
else
if ch <> STX then
begin
writeln('Invalid header byte (expected STX)');
done := true;
end;
until done or timedOut;
close(xferFile);
writeln(blockNo, ' blocks received, ', errorCount, ' checksum errors. ', ord(ch));
end
else
writeln('Invalid or no header received.', size);
end;
function getWordFromFile:integer;
function getCharFromFile:integer;
var c:char;
begin
if size > 0 then
begin
read(xferFile,c);
size := size - 1;
end
else
c := #0;
getCharFromFile := ord(c);
end;
begin
getWordFromFile := getCharFromFile shl 8;
getWordFromFile := (getWordFromFile or getCharFromFile) shl 8;
getWordFromFile := (getWordFromFile or getCharFromFile) shl 8;
getWordFromFile := (getWordFromFile or getCharFromFile);
end;
procedure sendword(w:integer);
var b3,b2,b1,b0:char;
begin
b0 := chr(w and 255);
w := w shr 8;
b1 := chr(w and 255);
w := w shr 8;
b2 := chr(w and 255);
w := w shr 8;
b3 := chr(w and 255);
write(b3,b2,b1,b0);
end;
procedure sendFile;
var ch:char;
w,cksum:integer;
wordCount:integer;
lastSize,lastPos:integer;
timedOut:boolean;
done:boolean;
begin
if length(filename) = 0 then
begin
writeln('Filename not set.');
exit;
end;
{ wait for start byte }
ch := conin;
if ch <> BEL then
begin
writeln('Invalid start character received.');
exit;
end;
open(xferFile, filename, ModeReadonly);
if IOResult(xferFile) <> 0 then
begin
writeln('Error opening file: ', ErrorStr(IOResult(xferFile)));
exit;
end;
size := filesize(xferFile);
done := false;
{ send size header: SOH, size word, inverted size word }
write(SOH);
sendword(size);
sendword(not size);
{ check for ACK }
waitForByte(ch, timedOut);
if timedOut then
writeln('Timeout sending size header ')
else
if ch <> ACK then
writeln('Error sending size header ', ord(ch))
else
repeat
lastPos := filepos(xferFile);
lastSize := size;
write(STX);
{ send a block: STX, 8 words, checksum word }
cksum := 0;
for wordCount := 1 to 8 do
begin
w := getWordFromFile;
cksum := calcChkSum(cksum, w);
sendword(w);
end;
sendword(cksum);
{ check for ACK/NAK }
waitForByte(ch, timedOut);
if timedOut then
begin
writeln('Timeout waiting for ACK');
done := true;
end
else
if ch = NAK then
begin
seek(xferFile, lastPos);
size := lastSize;
end
else
if ch = ACK then
begin
if size = 0 then done := true;
end
else
begin
writeln('Invalid reply after sending block');
done := true;
end;
until done;
write(EOT);
close(xferFile);
end;
procedure setFilename;
begin
write('Filename> ');
readln(filename);
end;
procedure listDirectory;
var volid:integer;
error:integer;
index:integer;
dirs:DirectorySlot;
begin
volid := findvolume(DefaultVolume);
if volid < 1 then
writeln('Volume ', DefaultVolume, ' not found.')
else
begin
openvolumeid(volid);
readdirfirst(volid, index, dirs, error);
while (index > 0) and (error = 0) do
begin
writeln(dirs.name);
readdirnext(volid, index, dirs, error);
end;
end;
writeln;
end;
begin
writeln('L) upload (receive) D) download (send).');
writeln('S) set filename Y) directory X) exit');
done := false;
repeat
write('> ');
read(cmd);
writeln;
case upcase(cmd) of
'L': receiveFile;
'D': sendFile;
'S': setFilename;
'X': done := true;
'Y': listDirectory;
else writeln('?');
end;
until done;
end.