GrapevineUser (Cedar: communication with registration server
GVNamesImpl.mesa
Andrew Birrell May 13, 1983 2:48 pm
DIRECTORY
ConvertUnsafe USING [ToRope],
GVBasics USING [Connect, GVString, maxGVStringLength, maxRNameLength, oldestTime, Password, Remark, RName, Timestamp],
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 [Find, Length, ROPE, Substr];
GVNamesImpl:
CEDAR
MONITOR
IMPORTS ConvertUnsafe, GVLocate, GVProtocol, PupDefs, Rope
EXPORTS GVNames =
BEGIN
Expand:
PUBLIC
PROC[name: GVBasics.RName, oldStamp: GVBasics.Timestamp ← GVBasics.oldestTime]
RETURNS[ GVNames.ExpandInfo ] =
BEGIN
info: GVNames.NameType;
list: GVNames.RListHandle;
count: INT;
stamp: GVBasics.Timestamp;
[info, stamp, list, count] ← GetCompound[name, oldStamp, Expand];
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
rcOK: BOOL ← FALSE;
GetEntryWork:
PROC[str: GVProtocol.Handle]
RETURNS[rc: GVProtocol.ReturnCode] =
BEGIN
[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
};
ReleaseStream[];
EXITS notQuite => { CloseStream[]; ReleaseStream[]; info ← info ← NEW[GVNames.GetEntryInfo ← [,,notFound[]]] };
END;
END;
GetConnect:
PUBLIC
PROC[name: GVBasics.RName]
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];
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]
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];
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]
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];
ReleaseStream[];
SELECT info
FROM
IN GVNames.StampInfo => RETURN[info];
ENDCASE => RETURN[protocolError];
END;
AuthenticateKey:
PUBLIC
PROC[name: GVBasics.RName, key: GVBasics.Password]
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];
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]
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];
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,
reporter: GVNames.ReporterProc ← NIL]
RETURNS[info: GVNames.Outcome] =
BEGIN
UpdateWork:
PROC[str: GVProtocol.Handle]
RETURNS[rc: GVProtocol.ReturnCode] =
BEGIN
GVProtocol.SendRSOperation[str, IdentifyCaller];
GVProtocol.SendRName[str, user];
GVProtocol.SendPassword[str, password];
GVProtocol.SendNow[str];
rc ← GVProtocol.ReceiveRC[str];
IF rc = [BadRName, group] THEN rc ← [BadRName,notFound];
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;
-- 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: BOOLEAN ← TRUE;
str: GVProtocol.Handle ← NIL; -- cached R-Server stream --
cacheAddr: PupDefs.PupAddress; -- address of cached stream --
addrHint: BOOLEAN ← FALSE; -- whether to use cached address as a hint --
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: BOOLEAN ← FALSE;
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 } };
END.