DIRECTORY
Atom USING [ MakeAtom ],
BasicTime USING [ FromNSTime, GMT, Now, nullGMT, Period, ToNSTime, Update ],
Commander USING [ CommandProc, Handle, Register ],
CommandTool USING [ NextArgument ],
Convert USING [ CardFromRope ],
DESFace USING [ CorrectParity, EncryptBlock ],
IO,
GVNames USING [ AuthenticateKey, GetConnect, Outcome ],
LoganBerry USING [ Error, Open, OpenDB, ReadEntry, WriteEntry ],
NameDB USING [ AttributeSeq, Authenticity ],
Process USING [ Detach ],
RefID USING [ nullID ],
RefTab USING [ Create, Fetch, Ref, Store ],
Rope USING [ Equal, Find, Length, ROPE, SkipOver, Substr ],
RPC USING [ Conversation, ConversationLevel, EncryptionKey, StartConversation, unencrypted ],
UserProfile USING [ Token ],
VoiceUtils USING [ CurrentRName, CurrentPasskey, NetAddress, NetAddressFromRope, nullNetAddress, RnameToRspec ]
;
NameDBImpl:
CEDAR
MONITOR
Lock as little as possible
IMPORTS Atom, BasicTime, Commander, CommandTool, Convert, DESFace, GVNames, IO, LoganBerry, Process, RefTab, Rope, RPC, UserProfile, VoiceUtils
Data
ROPE: TYPE= Rope.ROPE;
AttributeSeq: TYPE = NameDB.AttributeSeq; -- LoganBerry.Entry = LIST OF [$attribute, value]
OpenDB:
TYPE = LoganBerry.OpenDB;
nullDB: OpenDB = RefID.nullID;
Error:
PUBLIC
ERROR [ec:
ATOM, explanation: Rope.
ROPE ←
NIL] =
CODE;
Mostly just repeats LoganBerry errors.
DBHandle:
TYPE =
RECORD [
-- In case there ever have to be instances
dbPrefix: Rope.ROPE,
conversation: RPC.Conversation←RPC.unencrypted,
white: OpenDB←nullDB, -- Name/number database
blue: OpenDB←nullDB, -- Name/Lark/Workstation/Preferences database
red: OpenDB←nullDB -- GV cache database
];
dbHandle: DBHandle; -- There is but one instance now.
minQueryInterval:
INT ← 60;
Don't query Grapevine about any given value any more frequently.
conversationLevel: RPC.ConversationLevel ← ECB;
Procedures exported to NameDB
GetAttributes:
PUBLIC
PROC[rName:
ROPE, key:
ATOM←$rname, dbType:
ATOM←$white]
RETURNS [value: AttributeSeq←NIL] = {
ENABLE LoganBerry.Error => Error[ec, explanation];
openDB: OpenDB = DBFromType[dbType];
IF openDB#nullDB
THEN
value ← LoganBerry.ReadEntry[conv: dbHandle.conversation,
db: DBFromType[dbType].openDB, key: key, value: rName].entry;
};
GetAttribute:
PUBLIC
PROC[
rName: ROPE, attribute: ATOM, default: ROPE←NIL, key: ATOM←$rname]
RETURNS [value: ROPE←NIL] = {
ENABLE LoganBerry.Error => Error[ec, explanation];
dbKType: ATOM;
dbType: ATOM;
openDB: OpenDB;
attributes: AttributeSeq;
If key#rname, acquire the rname
[dbKType, , openDB] ← WhichDB[key];
IF dbKType#$noProbe
THEN {
attributes ← LoganBerry.ReadEntry[conv: dbHandle.conversation,
db: openDB, key: key, value: rName].entry;
rName ← FetchAttribute[attributes, $rname, NIL].value;
IF rName=NIL THEN RETURN;
};
If attribute is not in the same entry, use rName to get the entry it's in.
[dbType, , openDB] ← WhichDB[attribute];
SELECT dbType
FROM
= $noProbe => RETURN[rName];
# dbKType =>
attributes ← LoganBerry.ReadEntry[conv: dbHandle.conversation,
db: openDB, key: $rname, value: rName].entry;
ENDCASE;
Obtain the specified attribute
value ← FetchAttribute[attributes, attribute, default].value;
If we're dealing with a timed value, select one of timed.attribute and untimed.attribute
IF value.Equal["#"]
THEN {
value ← FetchAttribute[attributes, TAttr[attribute, "time"]].value;
value ← FetchAttribute[attributes, TAttr[attribute,
(IF value#NIL AND RelTime[value] > 0 THEN "timed" ELSE "untimed")], default].value;
};
If we're looking in the Grapevine directory (red pages), do the update.
IF dbType#$red THEN RETURN;
IF attributes=NIL THEN value ← DoGVUpdate[rName, attributes]
ELSE ConsiderGVUpdate[rName, attributes];
};
SetAttribute:
PUBLIC
PROC[rName:
ROPE, attribute:
ATOM, value:
ROPE] = {
ENABLE LoganBerry.Error => Error[ec, explanation];
dbType: ATOM;
openDB: OpenDB;
attributes: AttributeSeq;
wasTimed: BOOL←FALSE;
IF value#
NIL
THEN
SELECT attribute
FROM
$larkhost, $workstationhost => {
These secondary keys must be unique. Cancel any previous values.
oldRname: ROPE=GetAttribute[value, $rname, NIL, attribute];
IF oldRname#NIL THEN
SetAttribute[rName: oldRname, attribute: attribute, value: NIL];
};
ENDCASE;
[dbType, , openDB] ← WhichDB[attribute];
IF dbType=$noProbe
THEN
RETURN;
Can't alter primary key! And deletion of entire entries is done by hand.
IF attributes=NIL THEN attributes ← LIST[[$rname, rName]];
attributes ← GetAttributes[rName, $rname, dbType];
wasTimed ← Rope.Equal[FetchAttribute[attributes, attribute].value, "#"];
attributes ← StoreAttribute[attributes, attribute, value];
IF wasTimed
THEN {
-- Remove the timed values
attributes ← StoreAttribute[attributes, TAttr[attribute, "timed"], NIL];
attributes ← StoreAttribute[attributes, TAttr[attribute, "untimed"], NIL];
attributes ← StoreAttribute[attributes, TAttr[attribute, "time"], NIL];
};
LoganBerry.WriteEntry[conv: dbHandle.conversation,
db: openDB, entry: attributes, replace: TRUE];
};
SetAttributeTimed:
PUBLIC
PROC[
rName: Rope.ROPE, attribute: ATOM, value: Rope.ROPE,
time: BasicTime.GMTsicTime.nullGMT, interval: INT𡤀] = {
ENABLE LoganBerry.Error => Error[ec, explanation];
dbType: ATOM;
keyClass: KeyClass;
openDB: OpenDB;
attributes: AttributeSeq;
untimedValue: Rope.ROPE;
wasUntimed: BOOL;
IF time=BasicTime.nullGMT THEN time ← BasicTime.Update[BasicTime.Now[], interval];
[dbType, keyClass, openDB] ← WhichDB[attribute];
IF keyClass#$notKey
THEN
Error[$InvalidAttribute, "SetAttributeTimed cannot be applied to an attribute that is also a key"];
IF attributes=NIL THEN attributes ← LIST[[$rname, rName]];
attributes ← GetAttributes[rName, $rname, dbType];
untimedValue ← FetchAttribute[attributes, attribute].value;
wasUntimed ← ~Rope.Equal[untimedValue, "#"];
IF wasUntimed
AND untimedValue#
NIL
THEN
attributes ← StoreAttribute[attributes, TAttr[attribute, "untimed"], untimedValue];
attributes ← StoreAttribute[attributes, TAttr[attribute, "timed"], value];
attributes ← StoreAttribute[attributes, TAttr[attribute, "time"], TimeRope[time]];
IF wasUntimed THEN attributes ← StoreAttribute[attributes, attribute, "#"];
LoganBerry.WriteEntry[conv: dbHandle.conversation,
db: openDB, entry: attributes, replace: TRUE];
};
Authenticate:
PUBLIC
PROC[rName:
ROPE, key:
RPC.EncryptionKey]
RETURNS [authenticity: NameDB.Authenticity] = {
attributes: AttributeSeq ← GetAttributes[rName: rName, dbType: $red];
authRope: ROPE;
IF attributes = NIL THEN attributes ← LIST[[$rname, rName]];
authRope ← FetchAttribute[attributes, $authenticity].value;
authenticity ← IF authRope=NIL THEN NIL ELSE Atom.MakeAtom[authRope];
IF it exists, parse it up and produce a comparison with the proferred key?
SELECT authenticity
FROM
NIL, $unknown, $perhaps, $bogus =>
authenticity ← DoGVAuthenticate[rName, key, attributes];
$nonexistent => NULL;
$authentic => authenticity ← GVCheckKey[key, attributes].authenticity;
ENDCASE => ERROR;
ConsiderGVAuthenticate[rName, key, attributes];
If timeStamp is old, update authentication results in background
};
IsAuthenticated:
PUBLIC PROC[rName:
ROPE]
RETURNS [authenticity: NameDB.Authenticity ← $unknown] = {
attributes: AttributeSeq ← GetAttributes[rName, $rname, $red];
IF attributes#
NIL
THEN
authenticity ← Atom.MakeAtom[FetchAttribute[attributes, $authenticity, "perhaps"].value];
ConsiderGVUpdate[rName, attributes];
};
HostFromInstance:
PUBLIC PROC[instance: Rope.
ROPE]
RETURNS [VoiceUtils.NetAddress←VoiceUtils.nullNetAddress] = {
netAddressAsRope: ROPE = GetAttribute[instance, $connect];
IF netAddressAsRope#
NIL
THEN
RETURN[VoiceUtils.NetAddressFromRope[netAddressAsRope]];
};
Grapevine update.
ConsiderGVUpdate:
PROC[rName:
ROPE, attributes: AttributeSeq] = {
timeStamp: ROPE = FetchAttribute[attributes, TAttr[$connect, "timestamp"]].value;
IF timeStamp#NIL AND (-RelTime[timeStamp]) < minQueryInterval THEN RETURN;
TRUSTED { Process.Detach[FORK ForkGVUpdate[rName, attributes]]; };
};
ForkGVUpdate:
PROC[rName:
ROPE, attributes: AttributeSeq] = {
ENABLE Error => CONTINUE; -- Should report it, I guess.
[] ← DoGVUpdate[rName, attributes];
};
DoGVUpdate:
PROC[rName:
ROPE, attributes: AttributeSeq]
RETURNS[value: ROPE←NIL] = {
ENABLE LoganBerry.Error => Error[ec, explanation];
info: GVNames.Outcome←notFound;
[info, value] ← GVNames.GetConnect[rName];
IF info#individual THEN RETURN[NIL]; -- Didn't happen.
IF attributes=NIL THEN attributes ← LIST[[$rname, rName]];
IF value#NIL THEN attributes ← StoreAttribute[attributes, $connect, value];
attributes ← StoreAttribute[attributes, TAttr[$connect, "timestamp"], TimeRope[]];
LoganBerry.WriteEntry[conv: dbHandle.conversation,
db: dbHandle.red, entry: attributes, replace: TRUE];
};
ConsiderGVAuthenticate:
PROC[
rName: ROPE, key: RPC.EncryptionKey, attributes: AttributeSeq] = {
timeStamp: ROPE = FetchAttribute[attributes, TAttr[$key, "timestamp"]].value;
IF timeStamp#NIL AND (-RelTime[timeStamp]) < minQueryInterval THEN RETURN;
TRUSTED { Process.Detach[FORK ForkGVAuthenticate[rName, key, attributes]]; };
};
ForkGVAuthenticate:
PROC[rName:
ROPE, key:
RPC.EncryptionKey, attributes:AttributeSeq] = {
ENABLE Error => CONTINUE; -- Should report it, I guess.
[] ← DoGVAuthenticate[rName, key, attributes];
};
DoGVAuthenticate:
PROC[rName:
ROPE, key:
RPC.EncryptionKey, attributes: AttributeSeq]
RETURNS[authenticity: NameDB.Authenticity←$perhaps] = {
ENABLE LoganBerry.Error => Error[ec, explanation];
SELECT GVNames.AuthenticateKey[rName, key]
FROM
group, notFound => authenticity ← $nonexistent;
individual => authenticity ← $authentic;
protocolError => Error[$GVNamesProtocolViolation, "GV Names protocol violation"];
wrongServer, allDown => authenticity ← $unknown;
badPwd => authenticity ← $bogus;
ENDCASE => Error[$GVNamesProtocolViolation, "Unknown return code"];
IF attributes=NIL THEN attributes ← LIST[[$rname, rName]];
attributes ← StoreAttribute[attributes, $authenticity, IO.PutR[[atom[authenticity]]]];
attributes ← StoreAttribute[attributes, $key, GVCheckKey[key, attributes].keyRope];
attributes ← StoreAttribute[attributes, TAttr[$key, "timestamp"], TimeRope[]];
LoganBerry.WriteEntry[conv: dbHandle.conversation,
db: dbHandle.red, entry: attributes, replace: TRUE];
};
GVCheckKey:
PROC[key:
RPC.EncryptionKey, attributes: AttributeSeq]
RETURNS[keyRope: ROPE, authenticity: NameDB.Authenticity ← $perhaps] = TRUSTED {
encryptedKey: RPC.EncryptionKey;
cpKey: RPC.EncryptionKey ← key;
retrievedEncryptedKey: RPC.EncryptionKey;
retrievedKeyRope: ROPE;
rkrStream: IO.STREAM;
halves:
LONG
POINTER
TO
RECORD[firstHalf:
CARD, secondHalf:
CARD] ←
LOOPHOLE[LONG[@encryptedKey]];
DESFace.CorrectParity[LOOPHOLE[LONG[@cpKey]]];
DESFace.EncryptBlock[key: LOOPHOLE[cpKey], from: @key, to: LOOPHOLE[halves]];
keyRope ← IO.PutFR["%bB %bB", [cardinal[halves.firstHalf]], [cardinal[halves.secondHalf]]];
halves ← LOOPHOLE[LONG[@retrievedEncryptedKey]];
retrievedKeyRope ← FetchAttribute[attributes, $key].value;
IF retrievedKeyRope=NIL THEN RETURN;
rkrStream ← IO.RIS[retrievedKeyRope];
halves.firstHalf ← Convert.CardFromRope[IO.GetCedarTokenRope[rkrStream].token];
halves.secondHalf ← Convert.CardFromRope[IO.GetCedarTokenRope[rkrStream].token];
authenticity ← IF encryptedKey = retrievedEncryptedKey THEN $authentic ELSE $bogus;
};
Utilities
FetchAttribute:
PROC[
attributes: AttributeSeq, attribute: ATOM, default: ROPE←NIL]
RETURNS [value: ROPE, valueLoc: AttributeSeq←NIL] = {
value ← default;
FOR aL: AttributeSeq ← attributes, aL.rest
WHILE aL#
NIL
DO
IF aL.first.type = attribute THEN RETURN[aL.first.value, aL]; ENDLOOP;
};
ExtractAttribute:
PROC[
attributes: AttributeSeq, attribute: ATOM]
RETURNS [newAttributes: AttributeSeq←NIL] = {
IF attributes=NIL THEN RETURN;
IF attributes.first.type=attribute THEN RETURN[attributes.rest];
newAttributes ← attributes;
FOR aL: AttributeSeq ← attributes, aL.rest
WHILE aL.rest#
NIL
DO
IF aL.rest.first.type # attribute THEN LOOP;
aL.rest ← aL.rest.rest;
RETURN;
ENDLOOP;
};
StoreAttribute:
PROC[attributes: AttributeSeq, attribute:
ATOM, value:
ROPE]
RETURNS[newAttributes: AttributeSeq] = {
oldLoc: AttributeSeq;
IF value=
NIL
OR value.Length[]=value.SkipOver[skip: "\'015\'011"]
THEN
RETURN[ExtractAttribute[attributes, attribute]];
newAttributes ← attributes;
oldLoc ← FetchAttribute[attributes, attribute].valueLoc;
IF oldLoc#
NIL
THEN oldLoc.first ← [attribute, value]
ELSE newAttributes ← Append[attributes, LIST[[attribute, value]]];
};
Append:
PROC[
a1, a2: AttributeSeq]
RETURNS [newAttributes: AttributeSeq] = {
IF a2=NIL THEN RETURN[a1];
IF a1=NIL THEN RETURN[a2];
newAttributes ← a1;
FOR aL: AttributeSeq ← a1, aL.rest
WHILE aL#
NIL
DO
IF aL.rest#NIL THEN LOOP;
aL.rest ← a2;
RETURN;
ENDLOOP;
ERROR;
};
DBFromType:
PROC[dbType:
ATOM]
RETURNS [openDB: OpenDB ← nullDB] = {
IF dbHandle.white=nullDB THEN Open[];
RETURN[
SELECT dbType
FROM
$white => dbHandle.white,
$blue => dbHandle.blue,
$red => dbHandle.red,
ENDCASE => nullDB
];
};
WhichDB:
PROC[keyType:
ATOM]
RETURNS [
dbType: ATOM←$blue, keyClass: KeyClass←$notKey, openDB: OpenDB ← nullDB] = {
mer: MapEntry ← NARROW[dbMap.Fetch[keyType].val, MapEntry];
IF mer#
NIL
THEN {
dbType ← mer.dbType;
keyClass ← mer.keyClass;
};
openDB ← DBFromType[dbType];
};
RelTime:
PROC[ropeTime:
ROPE]
RETURNS [relativeToNow:
INT] = {
RETURN[BasicTime.Period[from: BasicTime.Now[],
to: BasicTime.FromNSTime[Convert.CardFromRope[ropeTime]]]];
};
TimeRope:
PROC [time: BasicTime.
GMT ← BasicTime.nullGMT]
RETURNS[nowRope:
ROPE] = {
IF time = BasicTime.nullGMT THEN time ← BasicTime.Now[];
RETURN[IO.PutFR["%bB", [cardinal[BasicTime.ToNSTime[time]]]]];
};
TAttr:
PROC[attribute:
ATOM, prefix: Rope.
ROPE]
RETURNS [tAttr:
ATOM] = {
RETURN[Atom.MakeAtom[IO.PutFR["%g.%g", [rope[prefix]], [atom[attribute]]]]]; };
Open:
PROC = {
ENABLE LoganBerry.Error => Error[ec, explanation];
prefixValue: IO.Value ← [rope[dbHandle.dbPrefix]];
index: INT ← Rope.Find[dbHandle.dbPrefix, "/", 0];
IF index#0 THEN index← -1;
IF dbHandle.conversation#
RPC.unencrypted
AND index#-1
THEN {
Possibly make an RPC Conversation for use in LoganBerry calls.
instanceValue: ROPE = dbHandle.dbPrefix.Substr[start: 1, len: index-1];
dotIndex: INT = Rope.Find[dbHandle.dbPrefix, ".", 1];
IF dotIndex # -1
AND dotIndex < index
THEN
dbHandle.conversation ← RPC.StartConversation[
VoiceUtils.CurrentRName[], VoiceUtils.CurrentPasskey[], instanceValue,
conversationLevel];
};
dbHandle.white ← LoganBerry.Open[
conv: dbHandle.conversation, dbName: IO.PutFR["%gWhitePages.df", prefixValue]];
dbHandle.blue ← LoganBerry.Open[
conv: dbHandle.conversation, dbName: IO.PutFR["%gBluePages.df", prefixValue]];
dbHandle.red ← LoganBerry.Open[
conv: dbHandle.conversation, dbName: IO.PutFR["%gRedPages.df", prefixValue]];
};
Shell commands
CmdDBOpen: Commander.CommandProc = {
[cmd: Commander.Handle] RETURNS [result: REF ANY ← NIL, msg: ROPE ← NIL]
dbHandle.dbPrefix ← CommandTool.NextArgument[cmd];
Open[];
};
CmdDBDetails: Commander.CommandProc = {
[cmd: Commander.Handle] RETURNS [result: REF ANY ← NIL, msg: ROPE ← NIL]
DoDetails[cmd, CommandTool.NextArgument[cmd]];
};
DoDetails:
PROC[cmd: Commander.Handle, rName:
ROPE] = {
attributes: AttributeSeq ← LIST [[$rname, rName]];
moreAttributes: AttributeSeq ← GetAttributes[rName, $rname, $white];
IF moreAttributes#NIL THEN attributes ← Append[attributes, moreAttributes.rest];
moreAttributes ← GetAttributes[rName, $rname, $blue];
IF moreAttributes#NIL THEN attributes ← Append[attributes, moreAttributes.rest];
moreAttributes ← GetAttributes[rName, $rname, $red];
IF moreAttributes#NIL THEN attributes ← Append[attributes, moreAttributes.rest];
FOR attr: AttributeSeq ← attributes, attr.rest
WHILE attr#
NIL
DO
IO.PutF[cmd.out, "%g: %g\n", [atom[attr.first.type]], [rope[attr.first.value]]];
ENDLOOP;
IO.PutChar[cmd.out, '\n];
};
CmdLarkDebug: Commander.CommandProc = {
[cmd: Commander.Handle] RETURNS [result: REF ANY ← NIL, msg: ROPE ← NIL]
larkNumber: ROPE ← CommandTool.NextArgument[cmd];
instance: ROPE ← CommandTool.NextArgument[cmd];
IF instance=
NIL
THEN
instance ← UserProfile.Token[key: "LarktestInstance", default: "Einstein.lark"];
DoOperate[cmd, larkNumber, "D", instance];
};
CmdLarkOperate: Commander.CommandProc = {
[cmd: Commander.Handle] RETURNS [result: REF ANY ← NIL, msg: ROPE ← NIL]
larkNumber: ROPE ← CommandTool.NextArgument[cmd];
instance: ROPE ← CommandTool.NextArgument[cmd];
IF instance=
NIL
THEN
instance ← UserProfile.Token[key: "ThrushClientServerInstance", default: "Strowger.lark"];
DoOperate[cmd, larkNumber, "O", instance];
};
DoOperate:
PROC[cmd: Commander.Handle, larkNumber:
ROPE, mode:
ROPE, instance:
ROPE] = {
[cmd: Commander.Handle] RETURNS [result: REF ANY ← NIL, msg: ROPE ← NIL]
rName: ROPE;
IF larkNumber=NIL THEN RETURN;
larkNumber ← IO.PutFR["173#%g#", [rope[larkNumber]]];
rName ← GetAttribute[larkNumber, $rname, NIL, $larkhost];
IF rName=NIL THEN RETURN;
SetAttribute[rName, $mode, mode];
SetAttribute[rName, $instance, instance];
DoDetails[cmd, rName];
};
Initialization
dbHandle.dbPrefix ← UserProfile.Token["ThrushClientServerInstance", "Strowger.Lark"];
dbHandle.dbPrefix ← IO.PutFR["/%g//%g/",
[rope[dbHandle.dbPrefix]], [rope[VoiceUtils.RnameToRspec[dbHandle.dbPrefix].simpleName]]];
Default prefix value; its default value is "/Strowger.lark//Strowger/"
Commander.Register["NameDBOpen", CmdDBOpen, "Specify location of system databases.
Usage: NameDBOpen <directoryPrefix>
Example: NameDBOpen /Strowger.lark//Strowger/
Example: NameDBOpen ///Users/self.pa/
(first example is the default)\n"];
Commander.Register["NameDBDetails", CmdDBDetails, "Print entire merged DB listing.
Usage: NameDBDetails <rName>\n"];
Commander.Register["LarkDebug", CmdLarkDebug, "Tune Lark into development server, debug mode.
Usage: LarkDebug <larkNumber> [<instance>]
Example: LarkDebug 110 (default instance is UserProfile.LarkTestInstance[])
Example: LarkDebug 110 Curie.lark\n"];
Commander.Register["LarkOperate", CmdLarkOperate, "Tune Lark into operational server, operational mode.
Usage: LarkOperate <larkNumber> [<instance>]
Example: LarkOperate 110 (default instance is UserProfile.ThrushClientServerInstance[])
Example: LarkOperate 110 Curie.lark\n"];