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: BOOLFALSE,
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: BOOLFALSE, 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[(details�he[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[GVDetailsR�tails^]];
};
LookupDetails: SAFE PROC[reallyDetails: REF ANY] = TRUSTED {
connect, forward: ROPE;
info: GVNames.ConnectInfo←notFound;
authentic: BOOLFALSE;
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: BOOLFALSE]
timeout=TRUE means that next attempt to fetch info about this RName consult Grapevine right away
RETURNS[rr: Results] = {
rName: ROPENIL;
rr←r;
IF details#NIL THEN {
details.valid←FALSE; rName�tails.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.ROPEIO.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.cacheSize�he.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"];
}.