NamesGVImpl.Mesa
Last modified by Swinehart, May 16, 1984 10:57:45 am PDT
Last Edited by: Pier, May 20, 1984 7:08:08 pm PDT
DIRECTORY
Commander USING [ CommandProc, Register ],
Convert USING [ RopeFromInt ],
Basics USING [ Comparison ],
BasicTime USING [ Update, Now, GMT, Period ],
GVNames USING [ AddForward, AuthenticateKey, ConnectInfo, CreateIndividual, Expand, GetConnect, Outcome, RemoveForward, RListHandle, SetConnect ],
IO,
Log USING [ CLog, MakeCLog, RedoCLog, Report, WriteCLog ],
List USING [ Length ],
MBQueue USING [ Create, Queue, QueueClientAction ],
Names,
Process USING [ Pause, SecondsToTicks ],
Rope USING [ Substr, Find, Cat, Concat, Equal, ROPE ],
RPC USING [ EncryptionKey ]
;
NamesGVImpl:
CEDAR MONITOR
IMPORTS BasicTime, Commander, Convert, GVNames, IO, Log, List, MBQueue, Names, Process, Rope
EXPORTS Names = {
OPEN IO;
ROPE: TYPE= Names.ROPE;
larkRegistry: ROPE=".Lark";
Results: TYPE=Names.Results;
GVDetails: TYPE=Names.GVDetails;
GVDetailsR: TYPE=Names.GVDetailsR;
ModeNotFound: TYPE=Names.ModeNotFound;
CacheBehavior: TYPE=Names.CacheBehavior;
DetailsCache: TYPE = REF DetailsCacheR;
DetailsCacheR:
TYPE =
RECORD [
cacheSize: NAT𡤀,
full: BOOL←FALSE,
cacheEntries: SEQUENCE len: NAT OF GVDetails
];
gvCacheLog: Log.CLog ← NIL;
cache: DetailsCache ← NEW[DetailsCacheR[200]];
cacheQueue: MBQueue.Queue←MBQueue.Create[];
numberOfQueuedActions: INT𡤀
assumedValidInterval: INT ← 60; -- GV updates done elsewhere may take a minute, and two calls, to be noticed.
assumedValidIntervalAfterChange: INT ← 600; -- GV updates done elsewhere may take a minute, and two calls, to be noticed. GV updates done elsewhere after a change here may take up to ten minutes to get noticed unless you explicitly flush the local cache.
realOldTime: BasicTime.GMT ← ValidUntil[-1];
GetDefaultRName:
PUBLIC
PROC[netAddress: Names.NetAddress]
RETURNS [name: Names.Rname] = {
results: Results;
details: GVDetails;
[results, details] ← GetDefaultDetails[netAddress];
RETURN[IF results=ok AND details.larkSpec THEN details.connect ELSE NIL];
};
GetDefaultDetails:
PUBLIC
PROC[netAddress: Names.NetAddress]
RETURNS [results: Results, details: GVDetails] = {
rNetAddress:
ROPE ←
Rope.Cat[Convert.RopeFromInt[netAddress.net, 8,
FALSE], "#",
Convert.RopeFromInt[netAddress.host, 8, FALSE], "#", larkRegistry];
[results, details] ← GetGVDetails[rNetAddress, ok, lookupAfter];
};
GetGVDetails:
PUBLIC
ENTRY
PROC[rName:
ROPE, mode: ModeNotFound, behavior: CacheBehavior, authenticate:
BOOL←
FALSE, key:
RPC.EncryptionKey←
NULL]
RETURNS [results: Results←ok, details: GVDetails←
NIL] =
TRUSTED {
index: NAT;
FOR i:
NAT
IN [0..cache.cacheSize)
DO
IF rName.Equal[(detailshe[i]).rName,
FALSE]
THEN
IF details.valid
AND behavior=lookInCacheOnly
THEN
~authenticated yet AND authenticate is a programming bug.
RETURN[ok, CopyDetails[cache[i]]] ELSE EXIT;
details←NIL;
ENDLOOP;
IF details=
NIL
THEN {
IF ~cache.full
AND cache.cacheSize>=cache.len
THEN {
cache.full←TRUE; Log.Report["GV Cache full", $System]; };
IF cache.full THEN index ← 0
ELSE { index ← cache.cacheSize; cache.cacheSize ← cache.cacheSize + 1; };
cache[index] ← (details ← NEW[GVDetailsR←[index, rName, realOldTime]]);
};
details.mustAuthenticate ← details.mustAuthenticate OR authenticate;
IF authenticate THEN details.key ← key;
details.canCreate ← ~details.valid AND mode=create;
SELECT behavior
FROM
lookupFirst => {
MBQueue.QueueClientAction[cacheQueue, LookupDetails, details];
numberOfQueuedActions←numberOfQueuedActions+1;
WAIT details.done;
};
lookupAfter =>
IF authenticate
AND ~details.authentic
OR
GMTComp[BasicTime.Now[], details.lastTimeValid]=greater THEN {
details.lastTimeValid ← ValidUntil[assumedValidInterval];
At most one try per interval
MBQueue.QueueClientAction[cacheQueue, LookupDetails, details];
numberOfQueuedActions←numberOfQueuedActions+1;
IF ~details.valid
OR (~details.authentic
AND details.mustAuthenticate)
THEN
WAIT details.done;
};
ENDCASE;
details.canCreate ← FALSE;
details ← CopyDetails[details];
IF details=
NIL
OR ~details.valid
THEN
RETURN[
IF mode=ok THEN notFound ELSE Report[notFound, notFound, details], details];
};
SetGVDetails:
PUBLIC ENTRY PROC[details: GVDetails] = {
UnparseDetails[details];
details.valid ← TRUE;
details.lastTimeValid ← ValidUntil[assumedValidIntervalAfterChange];
cache[details.cacheIndex] ← CopyDetails[details];
cache[details.cacheIndex].prevForward ← details.forward;
MBQueue.QueueClientAction[cacheQueue, SetNewDetails, details];
numberOfQueuedActions←numberOfQueuedActions+1;
};
SetNewDetails:
SAFE
PROC[reallyDetails:
REF
ANY] =
TRUSTED {
details: GVDetails ← NARROW[reallyDetails];
outcome: GVNames.Outcome;
tuneIndex, modeIndex: INT;
outcome ← GVNames.SetConnect[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, connect: details.connect];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE=> { []←Report[outcome, error, details, TRUE]; GOTO Done; };
IF details.prevForward#
NIL
THEN {
tuneIndex ← Rope.Find[s1: details.prevForward, s2: ";P"];
IF tuneIndex#-1
THEN {
--prevForward= ";P tune ; other" OR "other ;P tune ;"
IF tuneIndex = 0
THEN {
--prevForward= ";P tune ; other
outcome ← GVNames.RemoveForward[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, dest: Rope.Substr[base: details.prevForward, start: 0, len: (modeIndex←Rope.Find[s1: details.prevForward, s2: ";", pos1: 1])+1]];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE=> { []←Report[outcome, error, details, TRUE]; GOTO Done; };
outcome ← GVNames.RemoveForward[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, dest: Rope.Substr[base: details.prevForward, start: modeIndex+1 ]];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE=> { []←Report[outcome, error, details, TRUE]; GOTO Done; };
}
ELSE {
--prevForward= "other ;P tune ;"
outcome ← GVNames.RemoveForward[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, dest: Rope.Substr[base: details.prevForward, start: 0, len: tuneIndex]];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE=> { []←Report[outcome, error, details, TRUE]; GOTO Done; };
outcome ← GVNames.RemoveForward[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, dest: Rope.Substr[base: details.prevForward, start: tuneIndex]];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE=> { []←Report[outcome, error, details, TRUE]; GOTO Done; };
}
}
ELSE {
--prevForward= single forward string without ;P
outcome ← GVNames.RemoveForward[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, dest: details.prevForward];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE => { []←Report[outcome, error, details, TRUE]; GOTO Done; };
};
};
tuneIndex ← Rope.Find[s1: details.forward, s2: ";P"];
IF tuneIndex#-1
THEN {
--forward= ";P tune ; other" OR "other ;P tune ;"
IF tuneIndex = 0
THEN {
--forward= ";P tune ; other
outcome ← GVNames.AddForward[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, dest: Rope.Substr[base: details.forward, start: 0, len: (modeIndex←Rope.Find[s1: details.forward, s2: ";", pos1: 1])+1]];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE=> { []←Report[outcome, error, details, TRUE]; GOTO Done; };
outcome ← GVNames.AddForward[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, dest: Rope.Substr[base: details.forward, start: modeIndex+1 ]];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE=> { []←Report[outcome, error, details, TRUE]; GOTO Done; };
}
ELSE {
--forward= "other ;P tune ;"
outcome ← GVNames.AddForward[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, dest: Rope.Substr[base: details.forward, start: 0, len: tuneIndex]];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE=> { []←Report[outcome, error, details, TRUE]; GOTO Done; };
outcome ← GVNames.AddForward[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, dest: Rope.Substr[base: details.forward, start: tuneIndex]];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE=> { []←Report[outcome, error, details, TRUE]; GOTO Done; };
}
}
ELSE {
outcome ← GVNames.AddForward[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, dest: details.forward];
SELECT outcome
FROM
noChange, individual => NULL;
ENDCASE=> { []←Report[outcome, error, details, TRUE]; GOTO Done; };
};
GOTO Done;
EXITS
Done => numberOfQueuedActions ← numberOfQueuedActions - 1;
};
WaitForGV:
PUBLIC
PROC = {
UNTIL numberOfQueuedActions=0 DO Process.Pause[Process.SecondsToTicks[1]]; ENDLOOP;
};
CopyDetails:
INTERNAL
PROC[details: GVDetails]
RETURNS [copy: GVDetails] = {
IF details=NIL THEN RETURN[NIL];
RETURN[NEW[GVDetailsRtails^]];
};
LookupDetails:
SAFE
PROC[reallyDetails:
REF
ANY] =
TRUSTED {
connect, forward: ROPE;
info: GVNames.ConnectInfo←notFound;
authentic: BOOL←FALSE;
details: GVDetails ← NARROW[reallyDetails];
rName: ROPE = details.rName;
RecordDetails:
ENTRY
PROC =
CHECKED INLINE {
numberOfQueuedActions ← numberOfQueuedActions - 1;
IF details=NIL THEN RETURN;
details.rName ← rName;
details.connect ← connect;
details.forward ← details.prevForward 𡤏orward;
details.valid ← info=individual;
details.lastTimeValid ← ValidUntil[
IF details.canCreate AND details.valid THEN assumedValidIntervalAfterChange
ELSE assumedValidInterval];
details.authentic ← authentic;
details.canCreate ← FALSE;
ParseDetails[details];
NOTIFY details.done;
};
[info, connect] ← GVNames.GetConnect[rName];
SELECT info
FROM
individual => {
WITH GVNames.Expand[rName]
SELECT
FROM
group => {
IF List.Length[LOOPHOLE[members]] > 2 THEN []←Report[type, error, details]
ELSE FOR entries: GVNames.RListHandle ← members, entries.rest WHILE entries#NIL DO forward ← Rope.Concat[forward, entries.first] ; ENDLOOP;
};
noChange, individual => NULL;
ENDCASE => []←Report[type, error, details];
};
notFound => info ← DoCreate[details, "MFLFLX"];
ENDCASE => []←Report[info, error, details];
IF details=NIL OR ~details.mustAuthenticate THEN { RecordDetails[]; RETURN; };
SELECT (info←GVNames.AuthenticateKey[rName, details.key])
FROM
group, notFound => NULL;
individual => authentic←TRUE;
ENDCASE => []←Report[info, error, details];
RecordDetails[];
};
DoCreate:
PROC[details: GVDetails, password:
ROPE]
RETURNS [info: GVNames.Outcome] =
TRUSTED {
IF ~details.canCreate THEN RETURN;
info ← GVNames.CreateIndividual[
user: Names.CurrentRName[], password: Names.CurrentPasskey[],
individual: details.rName, newPwd: Names.CurrentPasskey[password]];
SELECT info
FROM
individual => NULL;
ENDCASE => []←Report[info, error];
};
ParseDetails:
PROC[details: GVDetails] = {
vs: IO.STREAM;
details.larkSpec ← details.userSpec ← FALSE;
IF ~details.valid THEN RETURN;
vs ← IO.RIS[details.forward];
details.mode ← 'O;
details.system ← "Lark";
details.type ← "LarkSmarts.Lark";
details.instance ← "Strowger.Lark";
details.range ← "1,0";
details.ringTune ← NIL;
WHILE ~vs.EndOf[]
DO
SELECT vs.GetChar[]
FROM
'P => { details.larkSpec ← TRUE; details.ringTune ← vs.GetTokenRope[breakProc: SemiTok].token; IF Rope.Equal[details.ringTune, ";"] THEN details.ringTune ← NIL; }; --P stands for PlayTune because R (ringTune) already taken
'M => { details.larkSpec ← TRUE; details.mode ← vs.GetChar[]; };
'R => { details.larkSpec ← TRUE; details.ringEnable ← vs.GetChar[]; };
'T => { details.larkSpec ← TRUE; details.system ← vs.GetTokenRope[breakProc: SemiTok].token; };
'I => {
details.type ← vs.GetTokenRope[breakProc: CommaTok].token;
[]←vs.GetTokenRope[breakProc: CommaTok];
details.instance ← vs.GetTokenRope[breakProc: CommaTok].token;
[]←vs.GetTokenRope[breakProc: CommaTok];
details.range ← vs.GetTokenRope[breakProc: SemiTok].token;
details.larkSpec←TRUE;
};
'X => {
details.userSpec ← TRUE;
[]←vs.GetChar[]; -- X.4473; is a typical entry. Don't remember what the '. is for.
details.telephoneExtension ← vs.GetTokenRope[breakProc: SemiTok].token;
};
'; => NULL;
ENDCASE => { [] ← vs.GetTokenRope[breakProc: SemiTok]; };
ENDLOOP;
};
UnparseDetails:
PROC[details: GVDetails] = {
IF details.larkSpec
THEN {
details.forward ←
IO.PutFR["M%g;T%s;I%s,%s,%s;",
char[details.mode], rope[details.system], rope[details.type],
rope[details.instance], rope[details.range]];
details.forward ← details.forward.Concat[IO.PutFR["R%g;P%g;", char[details.ringEnable], rope[details.ringTune]]];
}
ELSE
IF details.userSpec
THEN
details.forward ← IO.PutFR["X.%s", rope[details.telephoneExtension]];
};
SemiTok: IO.BreakProc = TRUSTED {RETURN[IF char='; THEN break ELSE other]; };
CommaTok: IO.BreakProc = TRUSTED {RETURN[IF char=', THEN break ELSE other]; };
Report:
PROC[outcome: GVNames.Outcome, r: Results, details: GVDetails←
NIL, timeout:
BOOL←
FALSE]
timeout=TRUE means that next attempt to fetch info about this RName consult Grapevine right away
RETURNS[rr: Results] = {
rName: ROPE←NIL;
rr←r;
IF details#
NIL
THEN {
details.valid←FALSE; rNametails.rName;
IF timeout THEN details.lastTimeValid ← realOldTime; -- next query will go to GV fer sherr
};
Log.Report[IO.PutFR["%s %s\n", rope[rName], rope[
SELECT outcome
FROM
noChange => "no change",
group => "group",
individual => "individual",
notFound => "not found",
protocolError => "protocol error",
wrongServer => "wrong server",
allDown => "all servers down",
badPwd => "bad password",
outOfDate => "out of date",
notAllowed => "not allowed",
ENDCASE => "??"]], $System]; };
GMTComp:
PUBLIC
PROC[t1, t2: BasicTime.
GMT]
RETURNS [c: Basics.Comparison] = {
period: INT = BasicTime.Period[t2, t1];
RETURN[IF period>0 THEN greater ELSE IF period=0 THEN equal ELSE less];
};
ValidUntil:
PROC[interval:
INT]
RETURNS [BasicTime.GMT] = {
RETURN[BasicTime.Update[BasicTime.Now[], interval]];
};
SaveGVCache:
PUBLIC
ENTRY
PROC = {
The following nonsense truncates the log file before beginning to write.
IF gvCacheLog=NIL THEN gvCacheLog ← Log.MakeCLog["GVCacheLog.txt", RestoreEntry];
IF gvCacheLog=NIL THEN { Log.Report["Couldn't create GVCacheLog.txt", $System]; RETURN; };
gvCacheLog.logStream.SetLength[0];
gvCacheLog.logStream.Close[];
gvCacheLog ← Log.MakeCLog["GVCacheLog.txt", RestoreEntry];
IF gvCacheLog=NIL THEN { Log.Report["Couldn't create GVCacheLog.txt", $System]; RETURN; };
FOR i:
NAT
IN [0..cache.cacheSize)
DO
IF cache[i].valid
THEN {
entry: Rope.
ROPE←
IO.PutFR["%s\n%s\n%s\n\n",
rope[cache[i].rName], rope[cache[i].connect], rope[cache[i].forward]];
Log.WriteCLog[gvCacheLog, entry];
};
ENDLOOP;
};
RestoreGVCache:
PUBLIC
ENTRY
PROC = {
IF gvCacheLog=NIL THEN gvCacheLog ← Log.MakeCLog["GVCacheLog.txt", RestoreEntry];
IF gvCacheLog=NIL THEN { Log.Report["Couldn't create GVCacheLog.txt", $System]; RETURN; };
FOR i: NAT IN [0..cache.len) DO cache[i]←NIL; ENDLOOP;
cache.cacheSize𡤀
Log.RedoCLog[gvCacheLog];
};
CmdSaveGVCache: Commander.CommandProc = {
SaveGVCache[];
};
CmdRestoreGVCache: Commander.CommandProc = {
RestoreGVCache[];
};
CmdFlushGVCache: Commander.CommandProc = {
FlushGVCache[];
};
CmdWaitForGV: Commander.CommandProc = {
WaitForGV[];
};
FlushGVCache:
PUBLIC
ENTRY
PROC = {
FOR i: NAT IN [0..cache.len) DO cache[i]←NIL; ENDLOOP;
cache.cacheSize𡤀
};
RestoreEntry:
SAFE
PROC[cLog: Log.CLog] = {
-- DoCLog command procedure
logStream: IO.STREAM = cLog.logStream;
rName: ROPE=ReadLine[logStream];
connect: ROPE=ReadLine[logStream];
forward: ROPE=ReadLine[logStream];
details: GVDetails ←
NEW[GVDetailsR←[cache.cacheSize, rName, realOldTime, connect, forward, forward]];
[]←ReadLine[logStream];
details.valid←TRUE;
ParseDetails[details];
cache[cache.cacheSize]tails;
cache.cacheSizehe.cacheSize+1;
};
ReadLine:
PROC[s:
IO.
STREAM]
RETURNS [ line:
ROPE] = {
RETURN[s.GetLineRope[]]; };
Initialization
Commander.Register["FlushGVCache", CmdFlushGVCache, "Flush GV Cache"];
Commander.Register["SaveGVCache", CmdSaveGVCache, "Save GV Cache"];
Commander.Register["RestoreGVCache", CmdRestoreGVCache, "Restore GV Cache"];
Commander.Register["WaitForGV", CmdWaitForGV, "Wait for GV communications to complete"];
}.