DIRECTORY
Atom USING [ GetPName, MakeAtom ],
BasicTime USING [ GMT, Now, OutOfRange, Period, Update ],
Commander USING [ CommandProc, Register ],
CommandTool USING [ NextArgument ],
GVBasics USING [ Password ],
IO,
LupineRuntime USING [ BindingError ],
MBQueue USING [ Create ],
NameDB USING [ Authenticate, GetAttribute ],
NamesRPC USING [ StartConversation ],
Nice,
Pup USING [ nullAddress ],
PupSocket USING [ CreateEphemeral, GetLocalAddress, Socket ],
RefID USING [ ID, Release, Reseal, Seal ],
Rope USING [ Concat, Cat, Length, ROPE, Substr ],
RPC USING [ AuthenticateFailed, EncryptionKey, ExportFailed, ImportFailed ],
ThNet USING [ CloseWhitePagesDatabase, HowToDial, InitWhitePagesDatabase, pd, WhitePagesEntry, WPListing, WPState ],
ThParty USING [ Credentials, NameReq, SmartsInterfaceName, SmartsInterfaceRecord, SmartsProperties ],
ThPartyPrivate USING [ AlertOne, DoAdvance, ConversationData, ConvState, GetConvState, MakeSubstitution, PartyBody, PartyData, PartyConflict, SmartsBody, SmartsData, UnsealParty, UnsealSmarts, VoiceParty ],
ThPartyRpcControl,
ThPartyMonitorImpl,
Thrush
USING [
ConversationID, Credentials, NB, NetAddress, noAddress, notReallyInConv, nullID, PartyID, PartyType, ROPE, SHHH, SmartsID, unencrypted ],
ThSmartsRpcControl USING [ ImportNewInterface ],
ThVersions USING [ ThrushVR ],
Triples USING [ Any, Erase, Foreach, ForeachProc, Item, Make, Select ],
TU USING [ MakeUnique, RefAddr ],
VoiceUtils USING [ CmdOrToken, CurrentPasskey, InstanceFromNetAddress, MakeAtom, MakeRName, Registrize, RnameToRspec, FindWhere, Problem, ProblemFR, RegisterWhereToReport, Report, ReportFR, WhereProc ]
;
Functions for obtaining existing parties, or phone numbers
GetParty:
PUBLIC
ENTRY PROC[shh:
SHHH←
NIL, partyID: PartyID,
rName: Thrush.ROPE, type: Thrush.PartyType←NIL]
RETURNS [nb: NB, newPartyID: PartyID] = {
ENABLE UNWIND=>NULL;
newParty: PartyData;
[nb, newParty] ← GetActiveParty[partyID, rName, type];
newPartyID ← Reseal[newParty];
};
GetParty.nb~$success
GetParty.nb~$noIdentSupplied
GetParty.nb~$noSuchParty2
GetParty.nb~$noSuchParty
GetParty.GetRelatedParty.nb~~$voiceTerminalBusy
GetParty.GetRelatedParty.nb~~$convStillActive
GetParty.GetIdleParty/GetRnameFromParty.nb~~$noNameAvailable
GetPartyFromFeepNum:
PUBLIC
ENTRY PROC[
shh: SHHH←none, partyID: PartyID, feepNum: Thrush.ROPE←NIL]
RETURNS [nb: NB←$success, newPartyID: PartyID←nullID] = {
ENABLE UNWIND=>NULL;
party: PartyData ← NIL;
rName:
ROPE ← ThNet.WhitePagesEntry [
wpState: wpState, name: feepNum, feep: TRUE, key: $officenumber].fullRName;
IF rName=NIL THEN RETURN[nb: $noSuchParty2];
[nb, party] ← GetActiveParty[partyID, rName, NIL];
newPartyID ← Reseal[party];
};
GetPartyFromFeepNum.nb~$success
GetPartyFromFeepNum.nb~$noSuchParty2
GetPartyFromFeepNum.GetActiveParty.nb~~$noIdentSupplied
GetPartyFromFeepNum.GetActiveParty.nb~~$noSuchParty2
GetPartyFromFeepNum.GetActiveParty.nb~~$noSuchParty
GetPartyFromFeepNum.GetRelatedParty.nb~~$voiceTerminalBusy
GetPartyFromFeepNum.GetRelatedParty.nb~~$convStillActive
GetPartyFromFeepNum.GetIdleParty/GetRnameFromParty.nb~~$noNameAvailable
GetPartyFromNumber:
PUBLIC
ENTRY
PROC[
shh: SHHH, partyID: PartyID, phoneNumber: Thrush.ROPE,
description: ROPE]
RETURNS [nb: NB, newPartyID: PartyID] = {
ENABLE UNWIND => NULL;
[nb, newPartyID] ← GetPartyFromNumberInt[partyID, phoneNumber, description, FALSE];
};
GetPartyFromNumber.nb~$success
GetPartyFromNumber.nb~$noSuchParty
GetPartyFromNumber.GetRelatedParty.nb~~$voiceTerminalBusy
GetPartyFromNumber.GetRelatedParty.nb~~$noSuchParty2
GetPartyFromNumber.GetRelatedParty.nb~~$convStillActive
GetPartyFromNumber.GetRnameFromParty.nb~~$noNameAvailable
GetNumbersForRName:
PUBLIC
PROC[shh:
SHHH←none, rName: Thrush.
ROPE]
RETURNS [fullRName: ROPE, number: ROPE, homeNumber: ROPE] = {
listing: ThNet.WPListing;
[fullRName, number, listing] ← ThNet.WhitePagesEntry[
wpState: wpState, name: rName, key: $officenumber];
IF listing#
NIL
THEN homeNumber ← ThNet.WhitePagesEntry[
wpState: wpState, key: $outsidenumber, listing: listing].entry;
};
GetActiveParty:
INTERNAL
PROC[partyID: PartyID, rName: Thrush.
ROPE,
type: Thrush.PartyType]
RETURNS [nb: NB←$success, newParty: PartyData←NIL] = {
Obtains a $service, $individual, or $telephone party, if available
party: PartyData ← ThPartyPrivate.UnsealParty[partyID];
[nb, newParty] ← GetActivePartyDo[party, rName, type];
It is important to allow one to get any party; attempts to connect a party to itself are caught by ThParty.Alert. Problem solved: couldn't call visitor from visitor's phone. (Poacher's home phone has already been substituted for poacher's when that's appropriate.)
};
GetActiveParty.nb~$success
GetActiveParty.nb~$noIdentSupplied
GetActiveParty.nb~$noSuchParty2
GetActiveParty.nb~$noSuchParty
GetActiveParty.GetRelatedParty.nb~~$voiceTerminalBusy
GetActiveParty.GetRelatedParty.nb~~$convStillActive
GetActiveParty.GetIdleParty/GetRnameFromParty.nb~~$noNameAvailable
GetActivePartyDo:
INTERNAL
PROC[party: PartyData, rName: Thrush.
ROPE,
type: Thrush.PartyType]
RETURNS [nb: NB←$success, newParty: PartyData←NIL] = {
number: ROPE;
listing: ThNet.WPListing;
wpRname: ROPE;
partyID: PartyID;
partyID ← Reseal[party];
IF rName=NIL THEN RETURN[$noIdentSupplied, NIL];
IF type =
NIL
THEN { -- request for $individual or $telephone
[nb, newParty] ← GetActiveParty[partyID, rName, $individual];
IF nb # $success
THEN
[nb, newParty] ← GetActiveParty[partyID, rName, $telephone]
ELSE
IF ThPartyPrivate.VoiceParty[newParty] = ThPartyPrivate.VoiceParty[party]
THEN {
nb=$success,type=$individual
altNewParty: PartyData; altNB: NB;
[altNB, altNewParty] ← GetActiveParty[partyID, rName, $telephone];
IF altNB = $success THEN RETURN[$success, altNewParty];
};
RETURN;
};
SELECT type
FROM
$service => { [nb, newParty] ← GetIdleParty[partyID, rName]; RETURN; };
$individual, $telephone => NULL; -- go on
$trunk => ERROR; -- until further notice
ENDCASE => ERROR;
newParty ←
NARROW[
Triples.Select[type, VoiceUtils.MakeAtom[VoiceUtils.Registrize[rName]], --party-- ] ];
IF newParty#
NIL
AND newParty.enabled
THEN {
SetPoaching[newParty, party];
RETURN;
};
IF type # $telephone THEN RETURN[nb: $noSuchParty2];
No active party; try to get a trunk line for that individual
[wpRname, number, listing] ← ThNet.WhitePagesEntry [
wpState: wpState, name: rName, key: $officenumber];
IF wpRname=NIL OR listing=NIL THEN RETURN[nb: $noSuchParty2];
IF number=
NIL
THEN number ← ThNet.WhitePagesEntry [
wpState: wpState, name: rName, key: $outsidenumber, listing: listing].entry;
IF number=NIL THEN RETURN[nb: $noSuchParty2];
[nb, partyID] ← GetPartyFromNumberInt[Reseal[party], number, wpRname, TRUE];
IF nb=$success THEN newParty ← ThPartyPrivate.UnsealParty[partyID];
};
GetActivePartyDo.nb~$ See GetActiveParty
GetIdleParty:
INTERNAL
PROC[partyID: PartyID, serviceName:
ROPE]
RETURNS [nb: NB, newParty: PartyData ← NIL] = {
Locates an idle $service party, if available
ENABLE UNWIND=>NULL;
found: BOOL←FALSE;
now: BasicTime.GMT = BasicTime.Now[];
myName: ROPE;
party: PartyData← ThPartyPrivate.UnsealParty[partyID];
serviceNameAtom:
ATOM = MakeServiceRname[serviceName].serviceRnameAtom;
e.g., $"text-to-speech.lark"
FindIdleParty: Triples.ForeachProc = {
[trip: Triples.TripleRec] RETURNS [continue: BOOLEAN ← TRUE]
A service party is available if it is enabled, not involved in a conversation, and either has never been reserved or the guaranteed reservation period has expired.
newParty ← NARROW[trip.val];
IF newParty.numConvs#0 THEN RETURN;
IF newParty.numConvs=0
AND newParty.enabled
AND
(newParty.reservedBy=Thrush.nullID OR
BasicTime.Period[from: newParty.reservationTime, to: now!
BasicTime.OutOfRange => CONTINUE] > pd.serviceGuaranteedReservationTime) THEN { found ← TRUE; RETURN[FALSE]; };
};
IF party = NIL THEN RETURN[nb: $noSuchParty];
[nb, myName,] ← GetRnameFromParty[party];
e.g., $"myself.pa"
IF nb # $success THEN RETURN[nb: nb, newParty: NIL];
Triples.Foreach[$service, serviceNameAtom, Triples.Any--party--, FindIdleParty];
IF ~found THEN RETURN[nb: $noSuchParty2];
Reserve the party
newParty.reservedBy ← partyID;
newParty.name ← myName;
newParty.reservationTime ← now;
};
GetIdleParty.nb~$success
GetIdleParty.nb~$noSuchParty
GetIdleParty.nb~$noSuchParty2
GetIdleParty.GetRnameFromParty.nb~~$noNameAvailable
ReleaseParty:
PUBLIC
ENTRY
PROC[shh:
SHHH←none, partyID: PartyID, targetPartyID: PartyID]
RETURNS [nb: NB←$success] = {
Always successful, since optional
targetParty: PartyData ← ThPartyPrivate.UnsealParty[targetPartyID];
IF targetParty=
NIL
OR targetParty.reservedBy#partyID
OR targetParty.type#$service
THEN RETURN;
targetParty.reservationTime ← BasicTime.Update[targetParty.reservationTime, -100!
BasicTime.OutOfRange => CONTINUE];
};
ReleaseParty.nb~$success
GetPartyFromNumberInt:
INTERNAL PROC[
partyID: PartyID, phoneNumber: Thrush.ROPE,
description: ROPE, trunkRequired: BOOL]
RETURNS [nb: NB←$success, newPartyID: PartyID←nullID] = {
Party description has not yielded a party, so phone number will have to do. Perhaps the phone number is an extension of an active $individual; if not, try to find an idle outgoing trunk.
isExt: BOOLEAN ← FALSE;
num: ROPE ← NIL;
party: PartyData ← ThPartyPrivate.UnsealParty[partyID];
trunkParty: PartyData;
myExtAtom: ATOM;
myExt: ROPE;
myExt is used as Intelnet authorization code.
IF party=NIL THEN RETURN[$noSuchParty, nullID];
myExtAtom ← GetRnameFromParty[party, $current].rAtom; -- rName for party.
myExtAtom ←
IF myExtAtom=
NIL
THEN
NIL
ELSE NARROW[Triples.Select[$Extension, myExtAtom, -- extension --]];
myExt ← IF myExtAtom=NIL THEN NIL ELSE Atom.GetPName[myExtAtom];
IF phoneNumber = NIL THEN RETURN[$noIdentSupplied, nullID];
A null phoneNumber is a request for a direct outside line connection.
[num, isExt] ← ThNet.HowToDial[phoneNumber, myExt];
IF isExt
AND ~trunkRequired
THEN {
atom: ATOM𡤊tom.MakeAtom[num]; -- phone extension
IF atom #NIL THEN atom ← NARROW[Triples.Select[$Extension, --party--, atom]];
IF atom#
NIL
THEN {
newParty: PartyData;
[nb, newParty]← GetActiveParty[partyID, Atom.GetPName[atom], NIL];
newPartyID ← Reseal[newParty];
SELECT nb FROM
$noSuchParty2 => NULL; -- extension info is there, but party isn't!
ENDCASE => RETURN;
};
};
Get Trunk Party
[nb, trunkParty] ← GetRelatedParty[party]; -- trunk if telephone/individual, or vice/versa
IF nb # $success
THEN {
party ← NARROW[Triples.Select[$Poaching, party, -- poachee--]];
IF party#NIL THEN [nb, trunkParty] ← GetRelatedParty[party];
};
IF nb # $success THEN RETURN;
trunkParty.outgoing ← num;
trunkParty.reservedBy ← partyID;
trunkParty.name ← description; -- starts out as name of trunk owner, changes on first use
newPartyID ← Reseal[trunkParty];
};
GetPartyFromNumberInt.nb~$success
GetPartyFromNumberInt.nb~$noSuchParty
GetPartyFromNumberInt.GetRelatedParty.nb~~$voiceTerminalBusy
GetPartyFromNumberInt.GetRelatedParty.nb~~$noSuchParty2
GetPartyFromNumberInt.GetRelatedParty.nb~~$convStillActive
GetPartyFromNumberInt.GetRnameFromParty.nb~~$noNameAvailable
GetRelatedParty:
INTERNAL
PROC[party: PartyData]
RETURNS [nb: NB, relatedParty: PartyData←NIL] = {
rAtom: ATOM;
[nb,,rAtom]←GetRnameFromParty[party, $owner];
IF nb # $success THEN RETURN; -- VERY strange
SELECT party.type
FROM
$individual, $telephone => {
relatedParty ← NARROW[Triples.Select[$trunk, rAtom, -- trunk party --]];
Make sure there's no "Front door/back door" conflict for trunk.
IF ThPartyPrivate.PartyConflict[relatedParty, party] THEN nb ← $voiceTerminalBusy;
};
$trunk =>
relatedParty ← NARROW[Triples.Select[$telephone, rAtom, -- telephone party --]];
ENDCASE;
IF relatedParty = NIL THEN nb ← $noSuchParty2
ELSE IF relatedParty.numConvs#0 THEN nb ← $convStillActive -- busy
ELSE IF ~relatedParty.enabled THEN nb ← $noSuchParty2; -- not available yet
};
GetRelatedParty.nb~$success
GetRelatedParty.nb~$voiceTerminalBusy
GetRelatedParty.nb~$noSuchParty2
GetRelatedParty.nb~$convStillActive
GetRelatedParty.GetRnameFromParty.nb~~$noSuchParty
GetRelatedParty.GetRnameFromParty.nb~~$noNameAvailable
Making new parties and smartses
Register:
PUBLIC
PROC[
shh: SHHH←none,
rName: ROPE←NIL, -- party identification
type: Thrush.PartyType ← $individual,
clonePartyID: PartyID,
interface: ThParty.SmartsInterfaceName, -- interface
properties: ThParty.SmartsProperties
] RETURNS [
nb: NB, credentials: ThParty.Credentials←[] ] = {
[nb, credentials] ← DoRegister[rName, type, clonePartyID, interface, NIL, nullID, properties];
};
Register.nb~$ See DoRegister
RegisterLocal:
PUBLIC
PROC[
shh: SHHH←none,
rName: ROPE←NIL, -- party identification
type: Thrush.PartyType ← $individual,
clonePartyID: PartyID,
interfaceRecord: ThParty.SmartsInterfaceRecord ← NIL,
smartsID: Thrush.SmartsID, -- Use this one, if given.
properties: ThParty.SmartsProperties
] RETURNS [
nb: NB, credentials: ThParty.Credentials←[] ] = {
[nb, credentials] ← DoRegister[rName, type, clonePartyID, [ ], interfaceRecord, smartsID, properties];
};
RegisterLocal.nb~$ See DoRegister
DoRegister:
PROC[
rName: ROPE←NIL, -- party identification
type: Thrush.PartyType ← $individual,
clonePartyID: PartyID,
interface: ThParty.SmartsInterfaceName, -- interface
interfaceRecord: ThParty.SmartsInterfaceRecord,
smartsID: Thrush.SmartsID, -- Use this one, if given.
properties: ThParty.SmartsProperties
] RETURNS [
nb: NB, credentials: ThParty.Credentials←[] ] = {
hostAtom:
ATOM = VoiceUtils.MakeAtom[
VoiceUtils.InstanceFromNetAddress[properties.netAddress, Atom.GetPName[type]]];
smarts: SmartsData←NARROW[Triples.Select[$SmartsForHost, hostAtom, -- smarts --]];
party: PartyData;
partyToSmartsShh: SHHH ← none;
IF clonePartyID#nullID
THEN {
[nb, credentials] ← RegisterClone[clonePartyID, hostAtom]; RETURN; };
IF smarts#
NIL
AND smarts.type#$service
THEN {
Deregister any previous smarts from this machine. When cloning to produce multiple smarts and parties for a multi-channel service, obviously don't want to do this.
Multi-channel services must deregister explicitly, since this won't handle it.
s: RefID.ID = Reseal[smarts];
nbregister[none, s];
IF nb # $success
THEN
VoiceUtils.ProblemFR[
"Couldn't deregister smarts %g (%g); will attempt to reregister anyway.",
$System, NIL, card[s], atom[nb]];
};
IF rName=NIL THEN RETURN[nb: $noIdentSupplied];
IF interfaceRecord=
NIL
THEN {
partyToSmartsShh ← NamesRPC.StartConversation[
caller: serverInterfaceName.instance,
callee: rName,
key: serverPassword,
level: --<<ECB>>--CBCCheck ! RPC.AuthenticateFailed =>
{ VoiceUtils.Problem["Authenticate failed", $System]; CONTINUE}];
IF partyToSmartsShh=NIL THEN RETURN[nb: $couldntAuthenticate];
interfaceRecord ← SmartsRpc.ImportNewInterface[
interfaceName: interface ! RPC.ImportFailed =>
{ VoiceUtils.Problem["Import failed", $System]; CONTINUE}];
IF interfaceRecord=NIL THEN RETURN[nb: $couldntConnect];
};
{ RegisterEntry: ENTRY PROC={
[nb, party] ← CreateParty[rName, type];
IF nb#$success
THEN
RETURN;
Includes setting RName access, poaching and visiting designations.
smarts←
NEW[ThPartyPrivate.SmartsBody←[
properties: properties,
type: type,
interface: interfaceRecord,
shh: partyToSmartsShh,
notifications: MBQueue.Create[],
pupSocket: PupSocket.CreateEphemeral[remote: Pup.nullAddress]
]];
IF smartsID#nullID
THEN {
suppliedSmarts: SmartsData ← ThPartyPrivate.UnsealSmarts[smartsID];
suppliedSmarts^ ← smarts^;
smarts ← suppliedSmarts;
};
smarts.properties.netAddress.socket ← PupSocket.GetLocalAddress[smarts.pupSocket].socket;
But IMPORTANT!! Net and host remain those supplied by the client. We're only providing a socket ID here that is in the range available to Bluejay. See ThPartyPrivate
TU.MakeUnique[$Smarts, party, smarts];
IF type#$service THEN TU.MakeUnique[$SmartsForHost, hostAtom, smarts]; --Figure out how to do this stuff for services
credentials.partyID ← RefID.Reseal[party];
credentials.smartsID ← RefID.Seal[smarts];
SetPoaching[party]; -- This is recomputed on every GetParty, included here for debugging ease.
}; RegisterEntry[]; };
VoiceUtils.Report[IO.PutFR["Register[%g, %g, %g] -> [%g, %g]",
rope[rName], atom[type], TU.RefAddr[ThPartyPrivate.UnsealParty[clonePartyID]],
TU.RefAddr[party], TU.RefAddr[smarts]]];
};
DoRegister.nb~$success
DoRegister.nb~$noIdentSupplied
DoRegister.nb~$couldntAuthenticate
DoRegister.nb~$couldntConnect
DoRegister.GetRnameFromParty.nb~~$noSuchParty -- $clone creation only
DoRegister.GetRnameFromParty.nb~~$noSuchSmarts -- $clone creation only
DoRegister.GetRnameFromParty.nb~~$noNameAvailable -- $clone creation only
DoRegister.CreateParty.nb~~$noIdentSupplied
AssignSmartsID:
PUBLIC
PROC
RETURNS [smartsID: SmartsID] = {
smarts: SmartsData ←
NEW[ThPartyPrivate.SmartsBody ← [
properties: [], interface: NIL, shh: NIL, notifications: NIL]];
RETURN[RefID.Seal[smarts]];
};
CheckIn:
PUBLIC
PROC[shh:
SHHH←none, credentials: ThParty.Credentials]
RETURNS [nb: NB←$success] = { NULL; };
Not implemented yet. See ThParty for description.
CheckIn.nb~$success -- unconditional
CreateParty:
INTERNAL
PROC[rName: Thrush.
ROPE, type: Thrush.PartyType]
RETURNS [nb: NB←$success, party: PartyData←NIL] = {
rName is a misnomer; it's the name of a service, without registry, if type is service.
partyRname: Thrush.ROPE = IF type # $service THEN rName ELSE MakeServiceRname[rName].serviceRname;
partyID: PartyID←nullID;
rAtom: ATOM=VoiceUtils.MakeAtom[partyRname];
extension: ATOM←NIL;
IF partyRname=NIL THEN RETURN[nb: $noIdentSupplied];
IF type # $service THEN party← NARROW[Triples.Select[type, rAtom, --party--]];
IF party#NIL THEN RETURN[$success, party];
party ← NEW[ThPartyPrivate.PartyBody ← [type: type, name: rName]];
SELECT type
FROM
$individual, $telephone, $service => {
-- Local telephone number
num: ROPE←GetNumbersForRName[rName: partyRname].number;
party.outgoing ← num;
IF num#NIL AND ~(([num,]←ThNet.HowToDial[num]).isLocalExtension) THEN num←NIL;
IF num#NIL THEN extension ← VoiceUtils.MakeAtom[num];
};
ENDCASE;
partyID ← RefID.Seal[party];
TU.MakeUnique[type, rAtom, party];
IF extension#NIL THEN TU.MakeUnique[$Extension, rAtom, extension];
This next is very type-specific. Too bad it has to be here.
IF type = $trunk
THEN {
telephoneParty: PartyData ← NARROW[Triples.Select[$telephone, rAtom, NIL]];
IF telephoneParty=NIL THEN ERROR;
TU.MakeUnique[$Other, telephoneParty, party];
};
};
CreateParty.nb~$success
CreateParty.nb~$noIdentSupplied
SetPoaching:
PUBLIC INTERNAL
PROC[party: PartyData, callingParty: PartyData←
NIL] = {
poacher, poachee: PartyData←NIL;
tempParty: PartyData ← NIL;
rName: ROPE ← party.name;
smarts: REF;
workstation: ATOM;
wsName: ROPE;
nb: NB;
{
SELECT party.type
FROM
$individual => {
poacheeID: PartyID;
poacher←party;
IF (smarts ← Triples.Select[$Smarts, poacher,
-- smarts --]) =
NIL
THEN {
VoiceUtils.Problem["No smarts connection?"]; RETURN; };
IF (workstation ←
NARROW[Triples.Select[$SmartsForHost,
-- host--,smarts]]) =
NIL
THEN {
VoiceUtils.Problem["No SmartsForHost?"]; RETURN; };
wsName ← Atom.GetPName[workstation]; -- $"3#456#individual" =>
wsName ←
wsName.Substr[len: wsName.Length[]-Rope.Length["individual"]]; -- "3#456#"
IF (rName ← NameDB.GetAttribute[wsName, $rname,
NIL, $workstationhost]) =
NIL
THEN GOTO Done;
poachee ← NARROW[Triples.Select[$telephone, VoiceUtils.MakeAtom[rName], --party--]];
IF poachee#NIL OR callingParty=NIL THEN GOTO Done;
There's no voice path for this individual. Try to create one using calling party's trunk.
IF party.outgoing = NIL THEN GOTO Done; -- No phone number?
[nb, poacheeID] ← GetPartyFromNumberInt[
partyID: Reseal[callingParty],
phoneNumber: party.outgoing,
description: rName,
trunkRequired: TRUE
];
IF nb#$success OR poacheeID=nullID THEN GOTO Done;
poachee ← ThPartyPrivate.UnsealParty[poacheeID];
};
$telephone => {
wsRope:
ROPE ←
NameDB.GetAttribute[rName, $workstationhost];
workstation ← VoiceUtils.MakeAtom[wsRope.Concat["individual"]];
IF workstation = NIL THEN GOTO Done;
poachee←party;
IF (smarts ← Triples.Select[$SmartsForHost, workstation,-- smarts--])= NIL THEN GOTO Done;
poacher ← NARROW[Triples.Select[$Smarts, -- party --, smarts]];
IF poacher = NIL THEN { VoiceUtils.Problem["No smarts connection?"]; RETURN; };
};
ENDCASE=>RETURN;
EXITS Done => NULL; };
Splice in poachees for poachers in any relationships that are ending, poachers for poachees in any that are beginning.
IF poachee#
NIL
THEN {
tempParty ← NARROW[Triples.Select[$Poaching, Any, poachee]];
IF tempParty#
NIL
AND tempParty.enabled
AND poacher=tempParty
AND poacher.enabled
THEN RETURN; -- Already set up as desired
ThPartyPrivate.MakeSubstitution[oldParty: tempParty, newParty: poachee]; -- If tempParty#NIL
Triples.Erase[$Poaching, Any, poachee];
};
IF poacher#
NIL
THEN {
tempParty ← NARROW[Triples.Select[$Poaching, poacher, Any]];
ThPartyPrivate.MakeSubstitution[oldParty: poacher, newParty: tempParty]; -- See MakeSubstitution
Triples.Erase[$Poaching, poacher, Any];
};
IF poacher=NIL OR poachee=NIL OR ~poacher.enabled OR ~poachee.enabled THEN RETURN;
Triples.Make[$Poaching, poacher, poachee];
ThPartyPrivate.MakeSubstitution[oldParty: poachee, newParty: poacher];
};
RegisterClone:
ENTRY
PROC[clonePartyID: PartyID, hostAtom:
ATOM]
RETURNS [nb: NB←$success, credentials: ThParty.Credentials←[]] = {
cloneParty: PartyData←ThPartyPrivate.UnsealParty[clonePartyID];
party: PartyData;
cloneSmarts, smarts: SmartsData;
rName: ROPE; rAtom: ATOM;
[nb, rName,rAtom] ← GetRnameFromParty[cloneParty, $owner];
IF nb#$success THEN RETURN;
IF cloneParty=NIL THEN RETURN[nb: $noSuchParty];
cloneSmarts ← NARROW[Triples.Select[$Smarts, cloneParty, -- Smarts --]];
IF cloneSmarts=
NIL
THEN {
VoiceUtils.Problem["No Smarts for party?"]; RETURN[nb: $noSuchSmarts]; };
party ← NEW[ThPartyPrivate.PartyBody ← [type: cloneParty.type, name: cloneParty.name]];
smarts←
NEW[ ThPartyPrivate.SmartsBody ← [
properties: cloneSmarts.properties,
type: cloneSmarts.type,
interface: cloneSmarts.interface,
shh: cloneSmarts.shh,
notifications: MBQueue.Create[],
pupSocket: PupSocket.CreateEphemeral[remote: Pup.nullAddress]
]];
smarts.properties.netAddress.socket ← PupSocket.GetLocalAddress[smarts.pupSocket].socket;
TU.MakeUnique[$Smarts, party, smarts];
IF party.type#$service THEN TU.MakeUnique[$SmartsForHost,hostAtom, smarts];
Triples.Make[cloneParty.type, rAtom, party];
credentials.partyID ← RefID.Seal[party];
credentials.smartsID ← RefID.Seal[smarts];
What should happen here anyhow, wrt SmartsForHost
VoiceUtils.Report[IO.PutFR["RegisterClone[%g, %g] -> [%g, %g]",
TU.RefAddr[party], TU.RefAddr[cloneParty], card[credentials.smartsID], TU.RefAddr[smarts] ], $Party, party];
};
RegisterClone.nb~$success
RegisterClone.nb~$noSuchParty
RegisterClone.nb~$noSuchSmarts
RegisterClone.GetRnameFromParty.nb~~$noNameAvailable
Enable:
PUBLIC
ENTRY
PROC[shh:
SHHH←none, smartsID: SmartsID]
RETURNS [nb: Thrush.NB] = { RETURN[Able[smartsID, TRUE]]; };
Enable.nb~$ See Able
Disable:
PUBLIC
ENTRY
PROC[shh:
SHHH←none, smartsID: SmartsID]
RETURNS [nb: Thrush.NB] = { RETURN[Able[smartsID, FALSE]]; };
Enable.nb~$ See Able
Able:
INTERNAL
PROC[smartsID: SmartsID, enabling:
BOOL]
RETURNS[nb: Thrush.NB←$success] = {
smarts: SmartsData=ThPartyPrivate.UnsealSmarts[smartsID];
party: PartyData;
IF smarts=NIL THEN RETURN[$noSuchSmarts];
IF (party←
NARROW[Triples.Select[$Smarts,
--party--, smarts]])=
NIL
THEN RETURN[$noSuchParty];
party.enabled← smarts.enablesParty← enabling;
SetPoaching[party];
};
Able.nb~success
Able.nb~noSuchSmarts
Able.nb~noSuchParty
GetPupSocket:
PUBLIC
PROC[partyID: PartyID]
RETURNS [PupSocket.Socket←
NIL] = {
Find socket structure associated with the voiceTerminal smarts currently associated with this party. See ThPartyPrivate discussion of sockets.
party: PartyData ← ThPartyPrivate.UnsealParty[partyID];
smarts: SmartsData = NARROW[Triples.Select[$Smarts, party, --smarts--]];
IF smarts=NIL THEN RETURN;
IF smarts.properties.role=$voiceTerminal THEN RETURN[smarts.pupSocket];
party ← NARROW[Triples.Select[$Poaching, party, --poachee--]];
IF party#NIL THEN RETURN[GetPupSocket[Reseal[party]]];
};
Deregister Smarts from Party, destroy both
Deregister:
PUBLIC
ENTRY
PROC[shh:
SHHH, smartsID: SmartsID]
RETURNS [nb: NB ← $success] = {
There's but one party per smarts, these days. When the Smarts goes, so goes the party. For the most part, we can (after idling all the conversations in which the party participates) merely erase all the interconnections between the party and smarts and other objects, and release their ID's.
Don't hang up calls for $individual parties with valid poachees, since a poachee can carry on a conversation by itself.
$Visiting implementations and hints for implementations are only suggestions, for now.
ENABLE UNWIND => NULL;
party: PartyData;
smarts: SmartsData ← ThPartyPrivate.UnsealSmarts[smartsID];
IF smarts=NIL THEN RETURN[$noSuchSmarts];
party ← NARROW[Triples.Select[$Smarts, --party--, smarts]];
IF party#
NIL
THEN {
IF party.type # $individual THEN IdleConversationsForParty[party, smartsID]
ELSE {
newParty: PartyData;
SetPoaching[party];
newParty ← NARROW[Triples.Select[$Poaching, party, Triples.Any]];
IF newParty#
NIL
AND newParty.type=$telephone
THEN
ThPartyPrivate.MakeSubstitution[party, newParty];
};
Transfer $Visitors from authenticated to assumed.
EraseAsObjOrVal[party];
VoiceUtils.ReportFR[
"DeleteParty[%g, %g]", $System, NIL, card[Reseal[party]], TU.RefAddr[party]];
IF ~RefID.Release[Reseal[party]] THEN nb ← $noSuchParty; -- ??
}
ELSE nb ← $noSuchParty;
EraseAsObjOrVal[smarts];
smarts.failed ← TRUE;
smarts.notifications.Flush[]; -- Abandon progress-report queue
They have to be allowed to empty themselves, so that the "reports outstanding" counts will be maintained correctly.
VoiceUtils.ReportFR[
"UnregisterSmarts[%g, %g]", $System, NIL, card[smartsID], TU.RefAddr[smarts]];
IF ~RefID.Release[smartsID] THEN ERROR; -- Having successfully dehandled above!
};
Deregister.nb~$success
Deregister.nb~$noSuchSmarts
Deregister.nb~$noSuchParty -- really a quite unreasonable error case!
IdleConversationsForParty:
INTERNAL
PROC[party: PartyData, smartsID: SmartsID] = {
convState: ThPartyPrivate.ConvState←NIL;
IdleOneConv: Triples.ForeachProc
-- [trip: TripleRec]
-- RETURNS [continue: BOOLEAN ← TRUE] -- ={
WITH trip.att SELECT FROM r: ThPartyPrivate.ConvState => convState ← r; ENDCASE => RETURN[TRUE];
IF convState.state<= Thrush.notReallyInConv THEN { convState ← NIL; RETURN[TRUE]; };
[
--nb, convEvent; do your best, I guess.--] ← ThPartyPrivate.DoAdvance[
credentials: [
partyID: Reseal[party],
smartsID: smartsID, -- This is very questionable
convID: NARROW[trip.obj, ThPartyPrivate.ConversationData].convID,
state: convState.state,
stateID: convState.stateID
],
state: $idle,
reportToAll: TRUE,
reason: $error,
comment: "Other telephone failed",
newInConv: FALSE
]; -- Idle party in conversation!!
RETURN[FALSE];
};
IdleRelated:
INTERNAL Triples.ForeachProc
-- [trip: TripleRec]
-- RETURNS [continue: BOOLEAN ← TRUE] -- = {
IdleConversationsForParty[NARROW[trip.obj], smartsID];
};
Idle any conversations in which the party is involved. Outside loop avoids Foreach/Erase conflicts.
DO
convState←NIL;
Triples.Foreach[Any, Any, party, IdleOneConv];
IF convState = NIL THEN EXIT;
ENDLOOP;
Apply the process recursively to poachers and visitors: if a poacher is in a conversation, the poachee is not recorded as being in the same one; similarly for visitors, at least at present. But the poachee or visitee is always vital to the conversation's existence, so out it goes.
This is still bogus. It won't work as well as older systems at dealing properly with Finch dropping out.
Triples.Foreach[$Poaching, Any, party, IdleRelated];
Triples.Foreach[$Visiting, Any, party, IdleRelated];
};
DescribeParty:
PUBLIC PROC[partyID: Thrush.PartyID, nameReq: ThParty.NameReq]
RETURNS[nb:
NB, description: Thrush.
ROPE,
type: Thrush.PartyType←$telephone, partner: Thrush.PartyID←nullID, visitee: Thrush.PartyID←nullID,
visitors: LIST OF Thrush.PartyID←NIL] = {
a: ATOM;
party: PartyData ← ThPartyPrivate.UnsealParty[partyID];
otherParty: PartyData;
AddVisitor: Triples.ForeachProc
-- [trip: TripleRec]
-- RETURNS [continue: BOOLEAN ← TRUE] -- = {
visitors ← CONS[Reseal[trip.obj], visitors];
};
[nb, description, a] ← GetRnameFromParty[ThPartyPrivate.UnsealParty[partyID], nameReq];
IF party=NIL THEN RETURN;
type ← party.type;
otherParty ← NARROW[Triples.Select[$Poaching, party, NIL]];
IF otherParty=NIL THEN otherParty ← NARROW[Triples.Select[$Poaching, NIL, party]];
IF otherParty#NIL THEN partner ← Reseal[otherParty];
otherParty ← NARROW[Triples.Select[$Visiting, party, NIL]];
IF otherParty#NIL THEN visitee ← Reseal[otherParty];
Triples.Foreach[$Visiting, NIL, party, AddVisitor];
};
DescribeParty.nb~$success
DescribeParty.GetRnameFromParty.nb~$noSuchParty
DescribeParty.GetRnameFromParty.nb~$noNameAvailable
Visiting Registration/Deregistration
noPassword: GVBasics.Password ← ALL[0];
Visit:
PUBLIC
ENTRY
PROC[shh:
SHHH←none, visitedParty: PartyID, visitingParty: PartyID,
visitingPassword: GVBasics.Password]
RETURNS [nb: Thrush.NB←$success] = {
visitor, visitee: PartyData;
visitorRname: Rope.ROPE;
InterceptVisitingCalls:
INTERNAL Triples.ForeachProc
-- [trip: TripleRec]
-- RETURNS [continue: BOOLEAN ← TRUE] -- = {
convState: ThPartyPrivate.ConvState←NIL;
credentials: Thrush.Credentials;
originator: PartyData;
conv: ThPartyPrivate.ConversationData;
WITH trip.att
SELECT
FROM
r: ThPartyPrivate.ConvState => convState ← r; ENDCASE => RETURN;
SELECT convState.state FROM $ringing, $notified => NULL; ENDCASE=>RETURN;
conv ← NARROW[trip.obj];
originator ← NARROW[Triples.Select[$Originator, conv, --originator--]];
IF originator=NIL THEN RETURN;
credentials.partyID ← Reseal[originator];
credentials.smartsID ← Reseal[Triples.Select[$Smarts, originator, --smarts--]];
credentials.convID ← conv.convID;
credentials.stateID ← ThPartyPrivate.GetConvState[conv, originator,
FALSE].stateID;
This would be bogus, except that Alert does not change the stateID of the caller.
[] ← ThPartyPrivate.AlertOne[credentials, Reseal[visitee], Reseal[visitor]];
};
visitor ← ThPartyPrivate.UnsealParty[visitingParty];
visitee ← ThPartyPrivate.UnsealParty[visitedParty];
IF visitee = NIL THEN RETURN[$noSuchParty];
IF visitor = NIL THEN RETURN[$noSuchParty2];
IF visitor = visitee THEN RETURN[$narcissism];
SELECT visitee.type FROM $individual => NULL; ENDCASE => RETURN[$visitingIllegal];
SELECT visitor.type
FROM
$individual, $telephone => NULL; ENDCASE => RETURN[$visitingIllegal];
Password protection
visitorRname ← GetRnameFromParty[visitor, $current].rName;
IF visitorRname=NIL THEN { VoiceUtils.Problem["No rname for party"]; RETURN[$noSuchParty2]; };
IF visitingPassword = noPassword
THEN {
IF VoiceUtils.MakeAtom[NameDB.GetAttribute[
visitorRname, $visitpasswordrequired, "false"]] # $false
THEN RETURN[$passwordNotValid];
}
ELSE
IF NameDB.Authenticate[visitorRname, visitingPassword] # $authentic
THEN
RETURN[$passwordNotValid];
[]←UnvisitInternal[visitingParty];
Triples.Make[$Visiting, visitor, visitee];
Worry about Patching the visitee into a ringing call!
If visitor is in a conversation, in state notified or ringing, simulate having the originator notify the visitee.
Triples.Foreach[Any, Any, visitor, InterceptVisitingCalls];
};
Visit.nb~$success
Visit.nb~$noSuchParty
Visit.nb~$noSuchParty2
Visit.nb~$narcissism
Visit.nb~$visitingIllegal
Visit.nb~$passwordNotValid
Unvisit:
PUBLIC ENTRY PROC[shh:
SHHH←none, visitedParty: PartyID, visitingParty: PartyID,
visitingPassword: GVBasics.Password]
Password not presently required or heeded.
RETURNS [nb: Thrush.NB←$success] = {
IF visitingParty=nullID THEN RETURN[$noSuchParty];
IF visitedParty=nullID THEN RETURN[$noSuchParty2];
RETURN[UnvisitInternal[visitedParty, visitingParty]];
};
Unvisit.nb~$success
Unvisit.nb~$noSuchParty -- Visiting Party ID null or invalid
Unvisit.nb~$noSuchParty2 -- Visitee Party ID null or invalid
Unvisit.nb~$noSuchVisiting -- specified visiting relationship did not hold.
UnvisitInternal:
INTERNAL PROC[visitedParty: PartyID, visitingParty: PartyID←nullID]
RETURNS[nb: Thrush.NB←$success] = {
visitor, visitee: PartyData;
visitor ← ThPartyPrivate.UnsealParty[visitingParty];
IF visitor=NIL THEN RETURN[$noSuchParty];
visitee ← NARROW[Triples.Select[$Visiting, visitor, -- visitee--]];
IF visitee=NIL THEN RETURN[$noSuchVisiting];
IF visitedParty#nullID AND Reseal[visitee]#visitedParty THEN RETURN[$noSuchVisiting];
Triples.Erase[$Visiting, visitor, Triples.Any];
ThPartyPrivate.MakeSubstitution[oldParty: visitor, newParty: visitee];
Only workstations would care when visiting ended. At present, there's no direct way to tell them.
};
ThPartyPrivate Implementations
DoDescribeParty:
PUBLIC
PROC[ party: PartyData, nameReq: ThParty.NameReq ]
RETURNS[ description: Thrush.ROPE←NIL] = {
type: Thrush.PartyType;
DescribeTrunk:
PROC
RETURNS[des:
ROPE] =
INLINE {
more: ROPE=IF party.outgoing#NIL THEN party.outgoing ELSE "outside line";
des ←
IF party.name=
NIL
THEN more
ELSE IO.PutFR["%s (%s)", IO.rope[party.name], IO.rope[more]];
};
Sel:
PROC[type: Thrush.PartyType, party: PartyData]
RETURNS [name: Thrush.
ROPE] = {
a: ATOM ← NARROW[Triples.Select[type, -- owner --, party]];
RETURN[IF a=NIL THEN NIL ELSE Atom.GetPName[a]];
};
IF party=NIL THEN RETURN;
type ← party.type;
IF nameReq=$none THEN RETURN;
description ←
SELECT type
FROM
$individual => (
SELECT nameReq
FROM
$address =>
IF (party←
NARROW[Triples.Select[$Poaching, party,
NIL]])=
NIL
THEN
NIL
ELSE DoDescribeParty[party, $address],
ENDCASE => party.name),
$telephone => party.name,
$trunk => (
SELECT nameReq
FROM
$owner => Sel[$trunk, party],
$description => DescribeTrunk[],
$current => party.name,
$address => party.outgoing,
ENDCASE=>ERROR),
$service => (
SELECT nameReq
FROM
$description =>
IO.PutFR["%s service (%s)",
rope[VoiceUtils.RnameToRspec[Sel[$service, party]].simpleName], rope[party.name]],
$address => party.name,
$owner, $current => Sel[$service, party],
ENDCASE=>ERROR),
ENDCASE => ERROR;
};
GetRnameFromParty:
PUBLIC PROC[party: PartyData, nameReq: ThParty.NameReq←$current]
RETURNS [nb: NB←$success, rName: Thrush.ROPE←NIL, rAtom: ATOM←NIL] = {
Returns (bogus) service name if type = $service
IF party=NIL THEN RETURN[nb: $noSuchParty];
rName ← DoDescribeParty[party, nameReq];
IF rName=NIL THEN nb ← $noNameAvailable
ELSE rAtom ← VoiceUtils.MakeAtom[rName];
};
GetRnameFromParty.nb~$success
GetRnameFromParty.nb~$noSuchParty
GetRnameFromParty.nb~$noNameAvailable
MakeServiceRname:
PUBLIC
PROC[serviceName: Thrush.
ROPE]
RETURNS [serviceRname: Thrush.ROPE, serviceRnameAtom: ATOM] = {
serviceRname ← Rope.Cat[serviceName, larkRegistry];
serviceRnameAtom ← VoiceUtils.MakeAtom[serviceRname];
};
Which party should we be when initiating calls?
GetCurrentParty:
PUBLIC
ENTRY PROC[shh: Thrush.
SHHH←none, smartsID: Thrush.SmartsID]
RETURNS [nb: NB←$success, partyID: Thrush.PartyID←Thrush.nullID] = {
smarts: REF ← ThPartyPrivate.UnsealSmarts[smartsID];
party: PartyData;
p: PartyData;
IF smarts=NIL THEN RETURN[nb: $noSuchSmarts];
party ← NARROW[Triples.Select[$Smarts, --party--, smarts]];
IF party#NIL AND ~party.enabled THEN RETURN[nb: $noSuchParty];
IF party=NIL THEN { VoiceUtils.Problem["No party?"]; RETURN[nb: $noSuchParty]; };
SetPoaching[party]; -- make sure $Poaching link is correct
IF (p←
NARROW[Triples.Select[$Poaching,
-- poacher --, party]])#
NIL
AND p.enabled THEN party ← p;
partyID ← Reseal[party];
};
GetCurrentParty.nb~$success
GetCurrentParty.nb~$noSuchSmarts
GetCurrentParty.nb~$noSuchParty
EraseAsObjOrVal:
INTERNAL
PROC[ref:
REF] = {
Triples.Erase[Any, ref, Any];
Triples.Erase[Any, Any, ref];
};
Initialization
ReportSystem: VoiceUtils.WhereProc = { s←
NIL; };
ReportParty: VoiceUtils.WhereProc = {
party: PartyData = NARROW[whereData];
smarts: SmartsData;
IF party=
NIL
OR
( smarts ← NARROW[Triples.Select[$VoiceTerminal, party, Any]] ) = NIL THEN RETURN;
SELECT smarts.properties.role
FROM
$voiceTerminal => s←Nice.LarkConLogStream[smarts.properties.netAddress]; ENDCASE;
IF s=NIL OR s=Nice.LarkConLogStream[Thrush.noAddress] THEN s←VoiceUtils.FindWhere[$System, NIL];
};
WhitePagesInit: Commander.CommandProc = {
treeName: ROPE;
IF wpState#NIL AND ThNet.CloseWhitePagesDatabase[wpState] THEN wpState←NIL;
treeName ← CommandTool.NextArgument[cmd];
IF wpState=
NIL
THEN wpState ← ThNet.InitWhitePagesDatabase[
treeName -- Optional, defaults supplied by WP impl.,
];
};
serverInterfaceName: ThPartyRpcControl.InterfaceName;
serverPassword: RPC.EncryptionKey;
ThPartyInit: Commander.CommandProc = {
ENABLE
RPC.ExportFailed => { VoiceUtils.Problem["ThParty export failed", $System]; GOTO Failed; };
instance:
ROPE = VoiceUtils.MakeRName[style: rName, name:
VoiceUtils.CmdOrToken[cmd: cmd, key: "ThrushServerInstance", default: "Strowger.Lark"]];
serverInterfaceName ← [
type: "ThParty.Lark",
instance: instance,
version: ThVersions.ThrushVR];
serverPassword ← VoiceUtils.CurrentPasskey[VoiceUtils.CmdOrToken[
cmd: cmd, key: "ThrushServerPassword", default: "MFLFLX"]];
VoiceUtils.RegisterWhereToReport[ReportSystem, $System];
VoiceUtils.RegisterWhereToReport[ReportParty, $Party];
ThPartyRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE];
ThPartyRpcControl.ExportInterface[
interfaceName: serverInterfaceName,
user: instance,
password: serverPassword];
VoiceUtils.ReportFR["Export[ThParty.Lark, %s]", $System, NIL, rope[serverInterfaceName.instance]];
EXITS
Failed => ThPartyRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE];
};
Commander.Register["WhitePages", WhitePagesInit, "WhitePages <TreeName (opt.)> -- Initialize and Open White Pages Database"];
Commander.Register["ThParty", ThPartyInit,
"ThParty <ExportInstance[Strowger]> <ServerPassword[...]>\nInitialize and Export ThParty"];
}.
Log
Swinehart, May 15, 1985 10:24:49 am PDT
Cedar 6.0
changes to: larkRegistry, GetParty, GetActiveParty, GeIdleParty, CreateParty, NewParty
Swinehart, May 16, 1985 9:18:28 am PDT
CommandToolExtras zapped.
Swinehart, May 22, 1985 12:01:41 pm PDT
ServiceName changes
recording => service
Jay => serviceName, to generalize
individual and service treated similarly in places
changes to: GetIdleParty, CreateParty, NewParty, DoDescribeParty, MakeServiceRname
changes to: CreateParty, ExistingParty, NewParty, DoDescribeParty
changes to: GetIdleParty
changes to: CreateParty, ExistingParty, NewParty
Swinehart, October 25, 1985 6:45:23 pm PDT
Handle => ID, NamesAndLog => VoiceUtils
changes to: DIRECTORY, ThPartyInitImpl, GetActiveParty, GetIdleParty, CreateParty, ExistingParty, DoRegister, RegisterClone, Deregister, EnterSmarts, MakeServiceRname, GetStdRingInfo, ReportSystem, ReportParty, ThPartyInit, ConversationID, PartyID, SmartsID, nullID, Reseal, GetParty, GetPartyFromFeepNum, GetPartyFromNumber, GetPartyFromNumberInt, GetTrunkParty, GetRname, Register, RegisterLocal, EnterSmartsE (local of EnterSmarts), Enable, Disable, DescribeParty, DoDescribeParty, GetCurrentParty, GetPartySmarts, SetStdRingInfo, SetRingEnable, NewParty
Swinehart, October 29, 1985 5:55:37 pm PST
Major revisions for visiting and poaching. See design document.
changes to: CreateParty, Register, RegisterLocal, DoRegister, EnterSmarts, Able, Deregister, PrepareRingTune, GetPartyFromNumber, GetRname, DescribeParty, GetActiveParty, GetIdleParty, GetPartyFromNumberInt, GetTrunkParty, Deregister, Deregister, EraseAsObj, Deregister