GrapevineUser (Cedar: communication with registration server
GVNamesImpl.mesa
Andrew Birrell August 25, 1983 5:09 pm
Last Edited by: Levin, September 22, 1983 11:57 am
Last Edited by: Taft, November 28, 1983 5:22 pm
DIRECTORY
ConvertUnsafe USING [ToRope],
GVBasics USING [Connect, GVString, maxGVStringLength, maxRNameLength, oldestTime, Password, Remark, RName, Timestamp],
GVExtras USING [],
GVLocate USING [FindRegServer, FoundServerInfo],
GVNames USING [ AuthenticateInfo, ConnectInfo, ExpandInfo, GetEntryInfo, GetEntryList, ListType, MembershipGrade, MemberInfo, MembershipLevel, Membership, NameType, Outcome, RemarkInfo, ReporterProc, RListHandle, SetServerInfo, StampInfo],
GVProtocol USING [Close, CreateStream, Enquire, Failed, GetSocket, Handle, IsLocal, ReceiveBoolean, ReceiveByte, ReceiveConnect, ReceiveCount, ReceivePassword, ReceiveRC, ReceiveRemark, ReceiveRList, ReceiveRName, ReceiveTimestamp, ReturnCode, RNameType, RSOperation, SendByte, SendNow, SendPassword, SendRName, SendRSOperation],
PupDefs USING [GetPupAddress, NameLookupErrorCode, PupAddress, PupAddressToRope, PupNameTrouble],
Rope USING [Equal, Find, Length, ROPE, Substr];
GVNamesImpl: CEDAR MONITOR
IMPORTS ConvertUnsafe, GVLocate, GVProtocol, PupDefs, Rope
EXPORTS GVExtras, GVNames =
BEGIN
Expand: PUBLIC PROC[name: GVBasics.RName, oldStamp: GVBasics.Timestamp ← GVBasics.oldestTime, reporter: GVNames.ReporterProc ← NIL] RETURNS[ GVNames.ExpandInfo ] =
BEGIN
info: GVNames.NameType;
list: GVNames.RListHandle;
count: INT;
stamp: GVBasics.Timestamp;
[info, stamp, list, count] ← GetCompound[name, oldStamp, Expand, reporter];
SELECT info FROM
noChange => RETURN[ [noChange[]] ];
group => RETURN[ [group[list, stamp, count]] ];
individual => RETURN[ [individual[list, stamp, count]] ];
notFound => RETURN[ [notFound[]] ];
protocolError => RETURN[ [protocolError[]] ];
wrongServer => RETURN[ [wrongServer[]] ];
allDown => RETURN[ [allDown[]] ];
ENDCASE => RETURN[ [protocolError[]] ];
END;
GetList: PUBLIC PROC[name: GVBasics.RName,
oldStamp: GVBasics.Timestamp,
list: GVNames.ListType,
reporter: GVNames.ReporterProc ← NIL]
RETURNS[ GVNames.MemberInfo ] =
BEGIN
info: GVNames.NameType;
members: GVNames.RListHandle;
count: INT;
stamp: GVBasics.Timestamp;
[info, stamp, members, count] ← GetCompound[name, oldStamp, SELECT list FROM
members => ReadMembers, owners => ReadOwners, friends => ReadFriends,
ENDCASE => ERROR,
reporter];
SELECT info FROM
noChange => RETURN[ [noChange[]] ];
group => RETURN[ [group[members, stamp, count]] ];
individual => RETURN[ [individual[]] ];
notFound => RETURN[ [notFound[]] ];
protocolError => RETURN[ [protocolError[]] ];
wrongServer => RETURN[ [wrongServer[]] ];
allDown => RETURN[ [allDown[]] ];
ENDCASE => RETURN[ [protocolError[]] ];
END;
ReceiveRList: PROC[str: GVProtocol.Handle] RETURNS[list: GVNames.RListHandle, count: INT] =
BEGIN
tail: GVNames.RListHandle ← NIL;
ReceiveRListWork: PROC[name: GVBasics.RName] =
BEGIN
new: GVNames.RListHandle = CONS[first: name, rest: NIL];
IF tail = NIL THEN list ← new ELSE tail.rest ← new;
tail ← new;
count ← count+1;
END;
list ← NIL;
count ← 0;
GVProtocol.ReceiveRList[str, ReceiveRListWork];
END;
ReceiveStampList: PROC[str: GVProtocol.Handle] RETURNS[list: LIST OF GVBasics.Timestamp] =
BEGIN
words: CARDINAL ← GVProtocol.ReceiveCount[str];
tail: LIST OF GVBasics.Timestamp ← NIL;
list ← NIL;
WHILE words > 0
DO stamp: GVBasics.Timestamp = GVProtocol.ReceiveTimestamp[str];
words ← words - SIZE[GVBasics.Timestamp];
IF tail = NIL
THEN { list ← CONS[stamp,NIL]; tail ← list }
ELSE { tail.rest ← CONS[stamp,NIL]; tail ← tail.rest };
ENDLOOP;
END;
GetCompound: PROC[name: GVBasics.RName, stamp: GVBasics.Timestamp, op: GVProtocol.RSOperation, reporter: GVNames.ReporterProc ← NIL] RETURNS[info: GVNames.NameType, newStamp: GVBasics.Timestamp, list: GVNames.RListHandle, count: INT] =
BEGIN
GetCompoundWork: PROC[str: GVProtocol.Handle]
RETURNS[rc: GVProtocol.ReturnCode] =
BEGIN
[rc, newStamp] ← GVProtocol.Enquire[str, op,
name, stamp];
END;
list ← NIL;
count ← 0;
info ← Enquire[name, GetCompoundWork, reporter];
IF ( info=individual AND ( op=Expand OR op=ReadMailboxes))
OR info=group
THEN [list, count] ← ReceiveRList[str ! GVProtocol.Failed => GOTO notQuite];
ReleaseStream[];
EXITS notQuite => { CloseStream[]; ReleaseStream[]; info ← allDown; }
END;
GetEntry: PUBLIC PROC[name: GVBasics.RName, reporter: GVNames.ReporterProc ← NIL] RETURNS[ rc: GVNames.NameType[group..allDown], info: REF GVNames.GetEntryInfo] =
BEGIN
[rc, info] ← AuthenticatedGetEntry[name: name, reporter: reporter];
END;
AuthenticatedGetEntry: PUBLIC PROC[user: GVBasics.RName ← NIL, password: GVBasics.Password ← [,,,], name: GVBasics.RName, reporter: GVNames.ReporterProc ← NIL] RETURNS[ rc: GVNames.NameType[group..allDown], info: REF GVNames.GetEntryInfo] =
BEGIN
rcOK: BOOLFALSE;
GetEntryWork: PROC[str: GVProtocol.Handle]
RETURNS[rc: GVProtocol.ReturnCode] =
BEGIN
rc ← PresentCredentials[user, password];
IF rc = [done, individual] THEN [rc, ] ← GVProtocol.Enquire[str, ReadEntry, name];
rcOK ← rc.code = done;
END;
info ← NEW[GVNames.GetEntryInfo ← [,,notFound[]]];
rc ← Enquire[name, GetEntryWork, reporter];
IF rcOK
THEN BEGIN
ENABLE GVProtocol.Failed => GOTO notQuite;
ReceiveFunnyString: PROC RETURNS[ GVBasics.GVString ] = TRUSTED
BEGIN
s: STRING = [GVBasics.maxGVStringLength];
s.length ← GVProtocol.ReceiveCount[str];
FOR i: CARDINAL IN [0..s.length) DO s[i] ← LOOPHOLE[GVProtocol.ReceiveByte[str]] ENDLOOP;
IF s.length MOD 2 # 0 THEN [] ← GVProtocol.ReceiveByte[str];
RETURN[ ConvertUnsafe.ToRope[s] ];
END;
ReceiveList: PROC RETURNS[ list: GVNames.GetEntryList ] =
BEGIN
list.current ← ReceiveRList[str].list;
list.currentStamps ← ReceiveStampList[str];
list.deleted ← ReceiveRList[str].list;
list.deletedStamps ← ReceiveStampList[str];
END;
type: GVProtocol.RNameType;
stamp: GVBasics.Timestamp;
[] ← GVProtocol.ReceiveCount[str]; -- number of components --
[] ← GVProtocol.ReceiveCount[str]; -- component length --
stamp ← GVProtocol.ReceiveTimestamp[str];
type ← LOOPHOLE[GVProtocol.ReceiveCount[str]];
info ← NEW[GVNames.GetEntryInfo ← SELECT type FROM
group => [,,group[,,,]],
individual => [,,individual[,,,,,]],
notFound => [,,notFound[]],
dead => [,,dead[]],
ENDCASE => ERROR];
info.name ← GVProtocol.ReceiveRName[str];
info.stamp ← stamp;
TRUSTED{
WITH i: info SELECT FROM
group =>
BEGIN
[] ← GVProtocol.ReceiveCount[str]; -- component length --
i.remarkStamp ← GVProtocol.ReceiveTimestamp[str];
i.remark ← ReceiveFunnyString[];
i.members ← ReceiveList[];
i.owners ← ReceiveList[];
i.friends ← ReceiveList[];
END;
individual =>
BEGIN
[] ← GVProtocol.ReceiveCount[str]; -- component length --
i.passwordStamp ← GVProtocol.ReceiveTimestamp[str];
i.password ← GVProtocol.ReceivePassword[str];
[] ← GVProtocol.ReceiveCount[str]; -- component length --
i.connectStamp ← GVProtocol.ReceiveTimestamp[str];
i.connect ← ReceiveFunnyString[];
i.forward ← ReceiveList[];
i.sites ← ReceiveList[];
END;
ENDCASE => NULL
};
END;
ReleaseStream[];
EXITS notQuite =>
{ CloseStream[]; ReleaseStream[]; info ← NEW[GVNames.GetEntryInfo ← [,,notFound[]]] };
END;
GetConnect: PUBLIC PROC[name: GVBasics.RName, reporter: GVNames.ReporterProc ← NIL] RETURNS[ info: GVNames.ConnectInfo, connect: GVBasics.Connect ← NIL ] =
BEGIN
ConnectWork: PROC[str: GVProtocol.Handle]
RETURNS[rc: GVProtocol.ReturnCode] =
BEGIN
[rc,] ← GVProtocol.Enquire[str, ReadConnect, name];
END;
info ← Enquire[name, ConnectWork, reporter];
IF info = individual
THEN connect ← GVProtocol.ReceiveConnect[str ! GVProtocol.Failed => GOTO notQuite ];
ReleaseStream[];
SELECT info FROM
IN GVNames.ConnectInfo => NULL;
ENDCASE => info ← protocolError;
EXITS notQuite => { CloseStream[]; ReleaseStream[]; info ← allDown }
END;
GetRemark: PUBLIC PROC[name: GVBasics.RName, reporter: GVNames.ReporterProc ← NIL]
RETURNS[ info: GVNames.RemarkInfo, remark: GVBasics.Remark ← NIL ] =
BEGIN
RemarkWork: PROC[str: GVProtocol.Handle]
RETURNS[rc: GVProtocol.ReturnCode] =
BEGIN
[rc,] ← GVProtocol.Enquire[str, ReadRemark, name];
END;
info ← Enquire[name, RemarkWork, reporter];
IF info = group
THEN remark ← GVProtocol.ReceiveRemark[str ! GVProtocol.Failed => GOTO notQuite ];
ReleaseStream[];
SELECT info FROM
IN GVNames.RemarkInfo => NULL;
ENDCASE => info ← protocolError;
EXITS notQuite => { CloseStream[]; ReleaseStream[]; info ← allDown }
END;
CheckStamp: PUBLIC PROC[name: GVBasics.RName,
oldStamp: GVBasics.Timestamp ← GVBasics.oldestTime,
reporter: GVNames.ReporterProc ← NIL]
RETURNS[ GVNames.StampInfo ] =
BEGIN
info: GVNames.NameType;
CheckStampWork: PROC[str: GVProtocol.Handle]
RETURNS[rc: GVProtocol.ReturnCode] =
BEGIN
[rc,] ← GVProtocol.Enquire[str, CheckStamp, name, oldStamp];
END;
info ← Enquire[name, CheckStampWork, reporter];
ReleaseStream[];
SELECT info FROM
IN GVNames.StampInfo => RETURN[info];
ENDCASE => RETURN[protocolError];
END;
AuthenticateKey: PUBLIC PROC[name: GVBasics.RName, key: GVBasics.Password, reporter: GVNames.ReporterProc ← NIL] RETURNS[GVNames.AuthenticateInfo ] =
BEGIN
AuthWork: PROC[str: GVProtocol.Handle]
RETURNS[rc: GVProtocol.ReturnCode] =
BEGIN
GVProtocol.SendRSOperation[str, Authenticate];
GVProtocol.SendRName[str, name];
GVProtocol.SendPassword[str: str, pw: key];
GVProtocol.SendNow[str];
rc ← GVProtocol.ReceiveRC[str];
END;
info: GVNames.NameType = Enquire[name, AuthWork, reporter];
ReleaseStream[];
SELECT info FROM
IN GVNames.AuthenticateInfo => RETURN[info];
ENDCASE => RETURN[protocolError];
END;
IsInList: PUBLIC PROC[name,
member: GVBasics.RName,
level: GVNames.MembershipLevel,
grade: GVNames.MembershipGrade,
acl: GVNames.ListType,
reporter: GVNames.ReporterProc ← NIL]
RETURNS[res: GVNames.Membership] =
BEGIN
IsInListWork: PROC[str: GVProtocol.Handle]
RETURNS[rc: GVProtocol.ReturnCode] =
BEGIN
GVProtocol.SendRSOperation[str, IsInList];
GVProtocol.SendRName[str, name];
GVProtocol.SendRName[str, member];
GVProtocol.SendByte[str, LOOPHOLE[grade]];
GVProtocol.SendByte[str, LOOPHOLE[acl]];
GVProtocol.SendByte[str, LOOPHOLE[level]];
GVProtocol.SendNow[str];
rc ← GVProtocol.ReceiveRC[str];
END;
info: GVNames.NameType = Enquire[name, IsInListWork, reporter];
SELECT info FROM
group =>
res ← IF GVProtocol.ReceiveBoolean[str ! GVProtocol.Failed => GOTO notQuite] THEN yes ELSE no;
individual, notFound => res ← notGroup;
ENDCASE => res ← allDown; -- includes various protocol errors --
ReleaseStream[];
EXITS notQuite => { CloseStream[]; ReleaseStream[]; res ← allDown; }
END;
Update: PUBLIC PROC[
user: GVBasics.RName,
password: GVBasics.Password,
op: GVProtocol.RSOperation,
target: GVBasics.RName,
value: GVBasics.GVString ← NIL,
newPwd: GVBasics.Password ← NULL,
list: GVNames.RListHandle ← NIL,
reporter: GVNames.ReporterProc ← NIL]
RETURNS[info: GVNames.Outcome] =
BEGIN
UpdateWork: PROC[str: GVProtocol.Handle]
RETURNS[rc: GVProtocol.ReturnCode] =
BEGIN
rc ← PresentCredentials[user, password];
IF rc = [done, individual]
THEN BEGIN
GVProtocol.SendRSOperation[str, op];
GVProtocol.SendRName[str, target];
SELECT op FROM
CreateGroup, DeleteIndividual, DeleteGroup, AddSelf, DeleteSelf =>
NULL;
ChangePassword, CreateIndividual =>
GVProtocol.SendPassword[str, newPwd];
ENDCASE =>
GVProtocol.SendRName[str, value];
GVProtocol.SendNow[str];
rc ← GVProtocol.ReceiveRC[str];
END;
END;
IF user.Length[] > GVBasics.maxRNameLength THEN RETURN[badPwd];
IF value.Length[] > GVBasics.maxGVStringLength
THEN value ← Rope.Substr[value, 0, GVBasics.maxGVStringLength];
target.Length[] is checked inside Enquire
info ← Enquire[target, UpdateWork, reporter];
ReleaseStream[];
END;
PresentCredentials: PROC [user: GVBasics.RName, password: GVBasics.Password] RETURNS [rc: GVProtocol.ReturnCode] =
BEGIN
IF user=NIL OR (Rope.Equal[user, authUser, FALSE] AND password=authPassword)
THEN rc ← [done, individual]
ELSE BEGIN
GVProtocol.SendRSOperation[str, IdentifyCaller];
GVProtocol.SendRName[str, user];
GVProtocol.SendPassword[str, password];
GVProtocol.SendNow[str];
rc ← GVProtocol.ReceiveRC[str];
SELECT rc FROM
[done, individual] => {authUser ← user; authPassword ← password};
[BadRName, group] => rc ← [BadRName,notFound];
ENDCASE;
END;
END;
-- There is a cache of one stream and one address. --
Access to this is under mutual exclusion, enforced by calls
-- on ClaimStream and ReleaseStream --
released: CONDITION;
free: BOOLEANTRUE;
str: GVProtocol.Handle ← NIL; -- cached R-Server stream --
cacheAddr: PupDefs.PupAddress; -- address of cached stream --
addrHint: BOOLEANFALSE; -- whether to use cached address as a hint --
authUser: GVBasics.RName ← NIL; -- authenticated credentials for Grapevine access
authPassword: GVBasics.Password;
ClaimStream: ENTRY PROC =
{ UNTIL free DO WAIT released ENDLOOP; free ← FALSE; };
ReleaseStream: ENTRY PROC =
{ free ← TRUE; NOTIFY released };
CleanUp: PUBLIC --NameInfoSpecialDefs-- PROC =
BEGIN
ClaimStream[];
-- optimize for R-Server/M-Server internal stream --
IF NOT GVProtocol.IsLocal[cacheAddr] THEN CloseStream[];
ReleaseStream[];
END;
"SetServer" allows a client (typically Maintain) to provide
a server name as a hint.
SetServer: PUBLIC PROC[name: Rope.ROPE] RETURNS[ GVNames.SetServerInfo ] =
BEGIN
pupName: Rope.ROPE;
addr: PupDefs.PupAddress;
trouble: PupDefs.NameLookupErrorCode;
IF name.Find["."] >= 0
THEN BEGIN
info: GVNames.ConnectInfo;
[info, pupName] ← GetConnect[name];
SELECT info FROM
individual => NULL;
allDown => RETURN[allDown];
ENDCASE => RETURN[badName];
END
ELSE pupName ← name;
BEGIN
addr ← PupDefs.GetPupAddress[GVProtocol.GetSocket[RSEnquiry], pupName !
PupDefs.PupNameTrouble => { trouble ← code; GOTO lookupFailed } ];
EXITS lookupFailed => RETURN[SELECT trouble FROM
noRoute => noRoute,
noResponse => allDown,
errorFromServer => badName,
ENDCASE => ERROR]
END;
ClaimStream[];
cacheAddr ← addr; addrHint ← TRUE;
ReleaseStream[];
RETURN[ok]
END;
Enquire: PROC[name: GVBasics.RName,
EnquiryWork: PROC[GVProtocol.Handle]
RETURNS[GVProtocol.ReturnCode],
reporter: GVNames.ReporterProc ← NIL ]
RETURNS[info: GVNames.Outcome] =
BEGIN
slightly subtle optimisation: If we don't have a stream, or the
stream we have times out, we'll need to get one to an R-Server for
GV. Getting one early might save us from having to call
-- FindRegServer later --
Create: PROC =
BEGIN
IF reporter # NIL THEN reporter[PupDefs.PupAddressToRope[cacheAddr]];
str ← GVProtocol.CreateStream[cacheAddr, RSEnquiry];
END;
rc: GVProtocol.ReturnCode;
oldBad: BOOLEANFALSE;
IF name.Length[] > GVBasics.maxRNameLength THEN RETURN[notFound];
ClaimStream[];
BEGIN
AcceptGV: PROC[addr: PupDefs.PupAddress]RETURNS[BOOLEAN] =
BEGIN
IF str # NIL THEN ERROR;
cacheAddr ← addr;
Create[! GVProtocol.Failed => GOTO failed ];
RETURN[TRUE];
EXITS failed => { CloseStream[]; RETURN[FALSE] }
END;
IF addrHint
THEN -- cached address hint set by client, so try it --
{ addrHint ← FALSE; CloseStream[]; [] ← AcceptGV[cacheAddr] };
IF str # NIL -- try cached stream first --
THEN BEGIN
rc ← EnquiryWork[str ! GVProtocol.Failed => GOTO streamGone];
EXITS streamGone => CloseStream[];
END;
IF str = NIL
THEN BEGIN
Don't use cached address, so that we return to a near server
Note: The following call of FindRegServer doesn't call us back recursively!
[] ← GVLocate.FindRegServer["x.GV", AcceptGV];
IF str = NIL THEN { info ← allDown; RETURN };
rc ← EnquiryWork[str ! GVProtocol.Failed => GOTO notThere];
END;
IF rc.code = WrongServer
THEN { oldBad ← TRUE; IF reporter # NIL THEN reporter["wrong server"] }
ELSE oldBad ← FALSE;
EXITS notThere => { CloseStream[]; oldBad ← TRUE; };
END;
IF oldBad
THEN BEGIN -- need to find the correct R-Server --
Accept: PROC[addr: PupDefs.PupAddress]RETURNS[BOOLEAN] =
BEGIN
ClaimStream[];
IF str # NIL AND cacheAddr # addr THEN CloseStream[];
IF str = NIL
THEN BEGIN
cacheAddr ← addr;
Create[! GVProtocol.Failed => GOTO failed];
END;
RETURN[TRUE];
EXITS failed => { CloseStream[]; ReleaseStream[]; RETURN[FALSE] }
END;
foundInfo: GVLocate.FoundServerInfo;
ReleaseStream[] -- for FindRegServer --;
TRUSTED {foundInfo ← GVLocate.FindRegServer[name, Accept]};
WITH foundInfo SELECT FROM
notFound => { info ← notFound; ClaimStream[]; RETURN };
allDown => { info ← allDown; ClaimStream[]; RETURN };
found =>
BEGIN
-- stream was claimed inside "Accept" --
rc ← EnquiryWork[str ! GVProtocol.Failed => GOTO down ];
EXITS down =>
{ CloseStream[]; info ← allDown; RETURN }
END;
ENDCASE => ERROR;
END;
info ← SELECT rc.code FROM
noChange => noChange,
done, BadRName =>
SELECT rc.type FROM
individual => individual,
group => group,
ENDCASE => notFound -- includes dead --,
BadOperation, BadProtocol => protocolError,
WrongServer => wrongServer,
AllDown => allDown,
BadPassword => badPwd,
outOfDate => outOfDate,
NotAllowed => notAllowed,
ENDCASE => allDown;
END;
CloseStream: PROC =
{ IF str # NIL THEN { GVProtocol.Close[str]; str ← NIL; authUser ← NIL } };
END.