file: STPsD.mesa - Simple/Stream Transfer Protocol
Common and protocol stuff in here
Edited by:
Mark,   Feb 12, 1981 11:39 PM
Smokey, 17-Jul-81 7:20:16
Karlton, 17-Jul-81 17:45:12
JGS,  14-Aug-81 10:35:26
Loretta 27-Oct-82 10:45:58
Daniels 21-Sep-82 13:07:12
Davirro 15-Dec-82 15:32:52
Andrew Birrell, June 1, 1983 5:39 pm
Last Edited by: Levin, August 9, 1983 9:59 am
Last Edited by: Schroeder, August 10, 1983 5:33 pm, but I only changed one character!
Last Edited by: MBrown, September 17, 1983 8:14 pm
Last Edited by: HGM, February 20, 1984 10:29:03 pm PST
DIRECTORY
BasicTime USING[ GMT, MonthOfYear, Unpack, Unpacked ],
Convert USING[ IntFromRope ],
IO USING[ EndOf, EndOfStream, GetBlock, GetChar, STREAM, PutChar, PutF, PutRope, ROS, RopeFromROS ],
PupStream USING[ConsumeMark, SendMark, StreamClosing, TimeOut],
Rope USING [Cat, Equal, Fetch, Find, FromRefText, Length, ROPE, Substr],
STP USING [DesiredProperties, Error, ErrorCode, FileInfo, Open, Type],
STPOps USING [
DestroyPList, ErrorCodeToSTPErrorCode, FileProperties, FilenameType,
GenerateErrorString, GenerateProtocolError, Handle, markAbort, markComment, markEOC, markHereIsPList, markIAmVersion, markNo, markYes, markYouAreUser, Object, PList, PListArray, ServerType, SmashClosed, UserProperties, ValidProperties];
STPsD: CEDAR PROGRAM
IMPORTS BasicTime, Convert, IO, PupStream, Rope, STP, STPOps
EXPORTS STP, STPOps =
BEGIN OPEN STPOps;
Exported Types
Object: PUBLIC TYPE = STPOps.Object;
Global Data
MarkEncountered: PUBLIC ERROR = CODE;
BadProperty: PUBLIC ERROR = CODE;
propertyStrings: PListArray = [
userName: "User-Name", userPassword: "User-Password",
connectName: "Connect-Name", connectPassword: "Connect-Password",
byteSize: "Byte-Size", type: "Type", size: "Size", directory: "Directory",
nameBody: "Name-Body", version: "Version", createDate: "Creation-Date",
readDate: "Read-Date", writeDate: "Write-Date", author: "Author",
eolConversion: "End-of-Line-Convention", account: "Account",
userAccount: "User-Account", device: "Device",
serverName: "Server-Filename"];
desiredPropString: Rope.ROPE = "Desired-property";
Commonly used stuff
Connect: PUBLIC PROCEDURE [stp: STPOps.Handle, name, password: Rope.ROPE] =
BEGIN
stp.userState[connectName] ← name;
stp.userState[connectPassword] ← password;
END;
IsOpen: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [yes: BOOLEAN] = {
yes ← stp.byteStream # NIL};
GetProperty: PUBLIC PROCEDURE [stp: STPOps.Handle, prop: ValidProperties] RETURNS [Rope.ROPE] =
{ RETURN[stp.plist[prop]]};
Login: PUBLIC PROCEDURE [stp: STPOps.Handle, name, password: Rope.ROPE] =
BEGIN
stp.userState[userName] ← name;
stp.userState[userPassword] ← password;
END;
SetHost: PUBLIC PROCEDURE [stp: STPOps.Handle, host: Rope.ROPE] =
{ stp.host ← host };
SetDirectory: PUBLIC PROCEDURE [stp: STPOps.Handle, directory: Rope.ROPE] =
{ stp.userState[directory] ← directory };
Procedures for doing FTP protocol operations
GetCommand: PUBLIC PROCEDURE [stp: STPOps.Handle]
RETURNS [mark: [0..256), code: CHARACTER, ps: Rope.ROPE] =
BEGIN
code ← 0C;
CheckConnection[stp];
mark ← MyGetMark[stp];
SELECT mark FROM
markAbort, markComment, markYouAreUser => ps ← CollectString[stp];
markIAmVersion, markNo, markYes => {code ← CollectCode[stp]; ps ← CollectString[stp]};
ENDCASE => GenerateProtocolError[stp, badMark, mark, code];
END;
GetHereIsAndPList: PUBLIC PROCEDURE [stp: STPOps.Handle, gobbleEOC: BOOLEANTRUE] =
BEGIN
mark: [0..256);
DO
SELECT (mark ← MyGetMark[stp]) FROM
markComment => stp.remoteString ← CollectString[stp];
markHereIsPList => {GetPList[stp, gobbleEOC, FALSE]; EXIT};
markNo =>
BEGIN
code: CHARACTER = CollectCode[stp];
errorCode: STP.ErrorCode = ErrorCodeToSTPErrorCode[requestRefused, code];
stp.remoteString ← CollectString[stp];
ErrorIfNextNotEOC[stp];
GenerateErrorString[stp, errorCode, stp.remoteString, code];
END;
ENDCASE => GenerateProtocolError[stp, badMark, mark, 0C];
ENDLOOP;
END;
GetPList: PUBLIC PROCEDURE [stp: STPOps.Handle, gobbleEOC: BOOLEAN, propertiesOk: BOOL] =
BEGIN
buffer: REF TEXT = NEW[TEXT[100]]; -- arbitrary choice of length
Record: PROC =
BEGIN
IF buffer.length # 0
THEN BEGIN
fragment: Rope.ROPE = Rope.FromRefText[buffer];
buffer.length ← 0;
IF which = property
THEN property ← property.Cat[fragment]
ELSE value ← value.Cat[fragment];
END;
END;
Append: PROC[c: CHAR] =
BEGIN
IF buffer.length = buffer.maxLength THEN Record[];
buffer[buffer.length] ← c; buffer.length ← buffer.length+1;
END;
property: Rope.ROPE;
value: Rope.ROPE;
parens: INT ← 0;
char: CHARACTER;
which: {property, value} ← property;
CheckConnection[stp];
stp.plist[nameBody] ← NIL;
DO
char ← MyGetChar[stp ! MarkEncountered => IF NOT propertiesOk THEN GOTO BadPList];
SELECT char FROM
'' =>
Append[MyGetChar[stp ! MarkEncountered => GOTO BadPList]];
'( =>
BEGIN parens ← parens + 1; Record[]; which ← property; property ← NIL; END;
' =>
IF which = property THEN BEGIN Record[]; which ← value; value ← NIL; END
ELSE Append[char];
') =>
BEGIN
Record[];
IF property.Length[] # 0 AND value.Length[] # 0 THEN
BEGIN
SetPListItem[
stp.plist, property, value ! BadProperty => CONTINUE];
property ← value ← NIL;
END;
IF (parens ← parens - 1) = 0 THEN EXIT;
END;
ENDCASE => Append[char];
ENDLOOP;
IF (property.Length[] # 0 AND value.Length[] # 0) THEN GOTO BadPList;
IF gobbleEOC AND MyGetMark[stp] # markEOC THEN GOTO BadPList;
EXITS
BadPList => {ResetPList[stp.plist]; GenerateProtocolError[stp, badPList, stp.mark]};
END;
PutCommand: PUBLIC PROCEDURE [
stp: STPOps.Handle, mark: [0..256), code: CHARACTER,
string: Rope.ROPE, sendEOC: BOOLEANTRUE] =
BEGIN
CheckConnection[stp];
PupStream.SendMark[stp.byteStream, mark];
stp.byteStream.PutChar[code];
IF string.Length[] # 0 THEN MyPutString[stp.byteStream, string];
IF sendEOC THEN PupStream.SendMark[stp.byteStream, markEOC];
END;
PutPList: PUBLIC PROCEDURE [
stp: STPOps.Handle, mark: [0..256), sendEOC: BOOLEANTRUE] =
BEGIN
state: {okay, tryOpen, triedOpen} ← okay;
DoIt: PROCEDURE =
BEGIN
CheckConnection[stp];
IF mark # 0B THEN PupStream.SendMark[stp.byteStream, mark];
stp.byteStream.PutChar['(];
FOR i: ValidProperties IN ValidProperties DO
IF stp.plist[i].Length[] # 0 THEN
PutPListItem[stp.byteStream, i, stp.plist[i]];
ENDLOOP;
IF stp.desiredProps # ALL[TRUE] THEN PutDesiredProps[stp];
stp.byteStream.PutChar[')];
IF sendEOC THEN PupStream.SendMark[stp.byteStream, markEOC];
END;
DO
DoIt[! PupStream.StreamClosing, PupStream.TimeOut =>
IF state = okay AND stp.host.Length[] # 0 THEN
{state ← tryOpen; CONTINUE}];
IF state # tryOpen THEN EXIT;
BEGIN
savedPList: PList ← stp.plist;
stp.plist ← NIL;
SmashClosed[stp]; -- Call outside catch so Pup monitor unlocked
[] ← STP.Open[stp, stp.host];
stp.plist ← DestroyPList[stp.plist];
stp.plist ← savedPList;
state ← triedOpen;
END;
ENDLOOP;
END;
Utility routines
CollectString: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS[ r: Rope.ROPENIL] =
BEGIN
buffer: REF TEXT = NEW[TEXT[100]]; -- arbitrary choice of length
DO buffer.length ← stp.byteStream.GetBlock[buffer, 0, buffer.maxLength];
r ← r.Cat[Rope.FromRefText[buffer]];
IF stp.byteStream.EndOf[] THEN EXIT;
ENDLOOP;
stp.mark ← PupStream.ConsumeMark[stp.byteStream];
stp.gotMark ← TRUE;
SELECT stp.mark FROM
markHereIsPList, markEOC => NULL;
ENDCASE => GenerateProtocolError[stp, eocExpected, stp.mark];
END;
CollectCode: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [code: CHARACTER] =
BEGIN
code ← 0C;
CheckConnection[stp];
code ← MyGetChar[stp ! MarkEncountered =>
GenerateProtocolError[stp, noCode, MyGetMark[stp]]];
END;
CheckConnection: PUBLIC PROCEDURE [stp: STPOps.Handle] =
BEGIN
WHILE stp.byteStream = NIL DO
SIGNAL STP.Error[stp, noConnection, "Please open a connection!", 0C] ENDLOOP
END;
ErrorIfNextNotYes: PUBLIC PROCEDURE [stp: STPOps.Handle] =
BEGIN
code: CHARACTER ← 0C;
mark: [0..256);
[mark, code, stp.remoteString] ← GetCommand[stp];
IF mark # markYes THEN GenerateErrorString[stp, requestRefused, stp.remoteString, code];
END;
ErrorIfNextNotEOC: PUBLIC PROCEDURE [stp: STPOps.Handle] =
BEGIN
mark: [0..256) ← MyGetMark[stp];
IF mark # markEOC THEN GenerateProtocolError[stp, eocExpected, mark];
END;
GetServerType: PUBLIC PROCEDURE [server: Rope.ROPE]
RETURNS [serverType: ServerType] =
BEGIN
RETURN[SELECT TRUE FROM
Rope.Equal[server, "MAXC", FALSE],
Rope.Equal[server, "MAXC2", FALSE] => tenex,
ENDCASE => ifs];
END;
LookAtMark: PUBLIC PROCEDURE [stp: STPOps.Handle]
RETURNS [[0..256)] =
BEGIN
IF ~stp.gotMark THEN
DO
[] ← MyGetChar[stp ! MarkEncountered => CONTINUE];
IF stp.gotMark THEN EXIT;
ENDLOOP;
RETURN[stp.mark]
END;
MyGetChar: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [char: CHARACTER] =
BEGIN
char ← stp.byteStream.GetChar[! IO.EndOfStream => GOTO end];
EXITS end =>
BEGIN
stp.mark ← PupStream.ConsumeMark[stp.byteStream];
stp.gotMark ← TRUE;
ERROR MarkEncountered;
END
END;
MyGetMark: PUBLIC PROCEDURE [stp: STPOps.Handle]
RETURNS [mark: [0..256)] =
BEGIN mark ← LookAtMark[stp]; stp.gotMark ← FALSE; RETURN[mark] END;
MyPutString: PUBLIC PROCEDURE [byteStream: IO.STREAM, string: Rope.ROPE] =
BEGIN
byteStream.PutRope[string];
END;
MyPutStringVal: PUBLIC PROCEDURE [byteStream: IO.STREAM, string: Rope.ROPE] =
BEGIN
FOR i: INT IN [0..string.Length[]) DO
char: CHAR = string.Fetch[i];
SELECT char FROM
'(, '), '' => byteStream.PutChar['']
ENDCASE => NULL;
byteStream.PutChar[char];
ENDLOOP;
END;
PropertyString: PUBLIC PROCEDURE [prop: STPOps.ValidProperties]
RETURNS [string: Rope.ROPE] = BEGIN RETURN[propertyStrings[prop]]; END;
SetCreateTime: PUBLIC PROCEDURE [stp: STPOps.Handle, creation: BasicTime.GMT] =
BEGIN
month: ARRAY BasicTime.MonthOfYear OF Rope.ROPE = [
January: "Jan", February: "Feb", March: "Mar", April: "Apr", May: "May", June: "Jun", July: "Jul", August: "Aug", September: "Sep", October: "Oct", November: "Nov", December: "Dec" ];
zoneIndex: TYPE = [4 .. 10];
zoneChars: ARRAY zoneIndex OF CHAR = ['A, 'E, 'C, 'M, 'P, 'Y, 'H];
unpack: BasicTime.Unpacked = BasicTime.Unpack[creation];
absZone: INTABS[IF unpack.dst = yes THEN unpack.zone - 60 ELSE unpack.zone];
Desired syntax is: "dd-mmm-yy hh:mm:ss zzz"
str: IO.STREAM = IO.ROS[];
str.PutF["%2d-%g-%02d %2d", [integer[unpack.day]], [rope[month[unpack.month]]], [integer[unpack.year MOD 100]], [integer[unpack.hour]] ];
str.PutF[":%02d:%02d ", [integer[unpack.minute]], [integer[unpack.second]] ];
IF (unpack.zone / 60 IN zoneIndex) AND absZone MOD 60 = 0
THEN BEGIN
str.PutChar[zoneChars[unpack.zone / 60]];
str.PutChar[IF unpack.dst = yes THEN 'D ELSE 'S];
str.PutChar['T];
END
ELSE str.PutF["%g%g:%02d", [character[IF unpack.zone < 0 THEN '- ELSE '+]],
[integer[absZone/60]], [integer[absZone MOD 60]] ];
stp.plist[createDate] ← str.RopeFromROS[];
END;
SetFileType: PUBLIC PROCEDURE [
stp: STPOps.Handle, fileType: STP.Type] =
BEGIN
stp.plist[type] ←
SELECT fileType FROM text => "Text", binary => "Binary", ENDCASE => NIL;
END;
SetByteSize: PUBLIC PROCEDURE [stp: STPOps.Handle, fileType: STP.Type] =
BEGIN
stp.plist[byteSize] ←
SELECT stp.serverType FROM
ifs, unknown, tenex => IF fileType = text THEN NIL ELSE "8",
ENDCASE => ERROR STP.Error[stp, undefinedError, NIL];
END;
StringToFileType: PUBLIC PROCEDURE [string: Rope.ROPE]
RETURNS [type: STP.Type] =
BEGIN
type ← SELECT TRUE FROM
Rope.Equal["Text", string, FALSE] => text,
Rope.Equal["Binary", string, FALSE] => binary,
ENDCASE => unknown;
END;
PList and FileInfo Utilities
NameToPList: PUBLIC PROCEDURE [plist: PList, name: Rope.ROPE, type: FilenameType] =
BEGIN
pos: INT ← 0;
length: INT = name.Length[];
IF pos >= length THEN RETURN;
IF name.Fetch[pos] = '[
THEN BEGIN
end: INT = name.Find["]"];
IF end > pos THEN { plist[device] ← name.Substr[pos+1, end-pos-1]; pos ← end+1 };
IF pos >= length THEN RETURN;
END;
IF name.Fetch[pos] = '<
THEN BEGIN
end: INT = name.Find[">"];
IF end > pos THEN { plist[directory] ← name.Substr[pos+1, end-pos-1]; pos ← end+1 };
IF pos >= length THEN RETURN;
END;
BEGIN
end: INT ← name.Find["!"];
IF end >= 0 THEN plist[version] ← name.Substr[end+1, length-end-1] ELSE end ← length;
plist[nameBody] ← name.Substr[pos, end-pos];
END;
END;
PListToName: PUBLIC PROCEDURE [plist: PList, type: FilenameType]
RETURNS [name: Rope.ROPENIL] =
BEGIN
dir: Rope.ROPE = plist[directory];
dirLength: INT = dir.Length[];
IF dirLength # 0 THEN
BEGIN
SELECT dir.Fetch[0] FROM
'> => name ← name.Cat[dir.Substr[1, dirLength-1]];
'< => name ← name.Cat[dir];
ENDCASE => name ← name.Cat["<", dir];
IF dir.Fetch[dirLength - 1] # '> THEN name ← name.Cat[">"];
END;
IF plist[nameBody].Length[] # 0 THEN name ← name.Cat[plist[nameBody]];
IF plist[version].Length[] # 0 THEN
name ← name.Cat[IF type = alto THEN "!" ELSE ";", plist[version]];
END;
MakeRemoteName: PUBLIC PROCEDURE [plist: PList, type: FilenameType] RETURNS [Rope.ROPE] = {
RETURN[IF plist[serverName].Length[] = 0 THEN PListToName[plist, type]
ELSE plist[serverName] ]};
PutPListItem: PUBLIC PROCEDURE [
byteStream: IO.STREAM, property: STPOps.ValidProperties, value: Rope.ROPE] =
BEGIN
byteStream.PutChar['(];
MyPutString[byteStream, PropertyString[property]];
byteStream.PutChar[' ];
MyPutStringVal[byteStream, value];
byteStream.PutChar[')];
END;
ResetPList: PUBLIC PROCEDURE [plist: PList] =
{ IF plist # NIL THEN plist^ ← ALL[NIL] };
GetFileInfo: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [STP.FileInfo] =
BEGIN
FOR i: STPOps.FileProperties IN STPOps.FileProperties DO
SELECT i FROM
directory => stp.info.directory ← stp.plist[i];
nameBody => stp.info.body ← stp.plist[i];
version => stp.info.version ← stp.plist[i];
author => stp.info.author ← stp.plist[i];
createDate => stp.info.create ← stp.plist[i];
readDate => stp.info.read ← stp.plist[i];
writeDate => stp.info.write ← stp.plist[i];
size =>
stp.info.size ←
IF stp.plist[i].Length[] = 0 THEN 0
ELSE Convert.IntFromRope[stp.plist[i]];
type => stp.info.type ← StringToFileType[stp.plist[i]];
ENDCASE;
ENDLOOP;
RETURN[stp.info];
END;
SetPListItem: PUBLIC PROCEDURE [plist: PList, property, value: Rope.ROPE] =
BEGIN
FOR i: STPOps.ValidProperties IN STPOps.ValidProperties DO
IF property.Equal[PropertyString[i], FALSE] THEN { plist[i] ← value; RETURN};
ENDLOOP;
ERROR BadProperty
END;
UserStateToPList: PUBLIC PROCEDURE [stp: STPOps.Handle] =
BEGIN OPEN stp;
FOR i: STPOps.UserProperties IN STPOps.UserProperties DO plist[i] ← userState[i] ENDLOOP;
END;
Desired Property stuff
SetDesiredProperties: PUBLIC PROC[stp: STPOps.Handle, props: STP.DesiredProperties] =
BEGIN
stp.desiredProps ← props
END;
GetDesiredProperties: PUBLIC PROC [stp: STPOps.Handle]
RETURNS [props: STP.DesiredProperties] =
BEGIN
RETURN[stp.desiredProps]
END;
PutDesiredProps: PUBLIC PROCEDURE [stp: STPOps.Handle] =
BEGIN
FOR i: STPOps.FileProperties IN STPOps.FileProperties DO
IF stp.desiredProps[i] THEN
PutDesiredPropsItem[stp.byteStream, PropertyString[i]]
ENDLOOP;
END;
PutDesiredPropsItem: PUBLIC PROCEDURE [
byteStream: IO.STREAM, value: Rope.ROPE] =
BEGIN
byteStream.PutChar['(];
MyPutString[byteStream, desiredPropString];
byteStream.PutChar[' ];
MyPutString[byteStream, value];
byteStream.PutChar[')];
END;
END. -- of STPsD