Cross-Relay attenuation

This commit is contained in:
Tony Garnock-Jones 2021-03-03 15:45:35 +01:00
parent 1cf0d9f89f
commit 94831bd140
4 changed files with 202 additions and 132 deletions

View File

@ -30,68 +30,68 @@ export type RewriteStage = Array<Rewrite>;
export type Rewrite = { pattern: Pattern, template: Template }; export type Rewrite = { pattern: Pattern, template: Template };
export const _CRec = Symbol.for('rec'); export const _CRec = Symbol.for('rec');
export const CRec = Record.makeConstructor<{label: Assertion, arity: number}, Ref>()( export const CRec = Record.makeConstructor<{label: Value<never>, arity: number}, never>()(
_CRec, ['label', 'arity']); _CRec, ['label', 'arity']);
export const _CArr = Symbol.for('arr'); export const _CArr = Symbol.for('arr');
export const CArr = Record.makeConstructor<{arity: number}, Ref>()( export const CArr = Record.makeConstructor<{arity: number}, never>()(
_CArr, ['arity']); _CArr, ['arity']);
export const _CDict = Symbol.for('dict'); export const _CDict = Symbol.for('dict');
export const CDict = Record.makeConstructor<{}, Ref>()( export const CDict = Record.makeConstructor<{}, never>()(
_CDict, []); _CDict, []);
export type ConstructorSpec = export type ConstructorSpec =
| Record<typeof _CRec, [Assertion, number], Ref> | Record<typeof _CRec, [Value<never>, number], never>
| Record<typeof _CArr, [number], Ref> | Record<typeof _CArr, [number], never>
| Record<typeof _CDict, [], Ref>; | Record<typeof _CDict, [], never>;
export const _PDiscard = Symbol.for('_'); export const _PDiscard = Symbol.for('_');
export const PDiscard = Record.makeConstructor<{}, Ref>()( export const PDiscard = Record.makeConstructor<{}, never>()(
_PDiscard, []); _PDiscard, []);
export const _PBind = Symbol.for('bind'); export const _PBind = Symbol.for('bind');
export const PBind = Record.makeConstructor<{name: string, pattern: Pattern}, Ref>()( export const PBind = Record.makeConstructor<{name: string, pattern: Pattern}, never>()(
_PBind, ['name', 'pattern']); _PBind, ['name', 'pattern']);
export const _PAnd = Symbol.for('and'); export const _PAnd = Symbol.for('and');
export const PAnd = Record.makeConstructor<{patterns: Array<Pattern>}, Ref>()( export const PAnd = Record.makeConstructor<{patterns: Array<Pattern>}, never>()(
_PAnd, ['patterns']); _PAnd, ['patterns']);
export const _PNot = Symbol.for('not'); export const _PNot = Symbol.for('not');
export const PNot = Record.makeConstructor<{pattern: Pattern}, Ref>()( export const PNot = Record.makeConstructor<{pattern: Pattern}, never>()(
_PNot, ['pattern']); _PNot, ['pattern']);
export const _Lit = Symbol.for('lit'); export const _Lit = Symbol.for('lit');
export const Lit = Record.makeConstructor<{value: Assertion}, Ref>()( export const Lit = Record.makeConstructor<{value: Value<never>}, never>()(
_Lit, ['value']); _Lit, ['value']);
export const _PCompound = Symbol.for('compound'); export const _PCompound = Symbol.for('compound');
export const PCompound = export const PCompound =
Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary<Pattern, Ref>}, Ref>()( Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary<Pattern, never>}, never>()(
_PCompound, ['ctor', 'members']); _PCompound, ['ctor', 'members']);
export type Pattern = export type Pattern =
| Record<typeof _PDiscard, [], Ref> | Record<typeof _PDiscard, [], never>
| Record<typeof _PBind, [string, Pattern], Ref> | Record<typeof _PBind, [string, Pattern], never>
| Record<typeof _PAnd, [Pattern[]], Ref> | Record<typeof _PAnd, [Pattern[]], never>
| Record<typeof _PNot, [Pattern], Ref> | Record<typeof _PNot, [Pattern], never>
| Record<typeof _Lit, [Assertion], Ref> | Record<typeof _Lit, [Value<never>], never>
| Record<typeof _PCompound, [ConstructorSpec, Dictionary<Pattern, Ref>], Ref>; | Record<typeof _PCompound, [ConstructorSpec, Dictionary<Pattern, never>], never>;
export const _TRef = Symbol.for('ref'); export const _TRef = Symbol.for('ref');
export const TRef = Record.makeConstructor<{name: string}, Ref>()( export const TRef = Record.makeConstructor<{name: string}, never>()(
_TRef, ['name']); _TRef, ['name']);
export const _TCompound = Symbol.for('compound'); export const _TCompound = Symbol.for('compound');
export const TCompound = export const TCompound =
Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary<Template, Ref>}, Ref>()( Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary<Template, never>}, never>()(
_TCompound, ['ctor', 'members']); _TCompound, ['ctor', 'members']);
export type Template = export type Template =
| Record<typeof _TRef, [string], Ref> | Record<typeof _TRef, [string], never>
| Record<typeof _Lit, [Assertion], Ref> | Record<typeof _Lit, [Value<never>], never>
| Record<typeof _TCompound, [ConstructorSpec, Dictionary<Template, Ref>], Ref>; | Record<typeof _TCompound, [ConstructorSpec, Dictionary<Template, never>], never>;
export type Bindings = { [name: string]: Assertion }; export type Bindings = { [name: string]: Assertion };
@ -312,7 +312,7 @@ export function match(p: Pattern, v: Assertion): Bindings | null {
case _CDict: case _CDict:
if (!Dictionary.isDictionary<Assertion, Ref>(v)) return false; if (!Dictionary.isDictionary<Assertion, Ref>(v)) return false;
for (const [key, pp] of members) { for (const [key, pp] of members) {
const vv = v.get(key); const vv = v.get(key as Assertion);
if (vv === void 0) return false; if (vv === void 0) return false;
if (!walk(pp, vv)) return false; if (!walk(pp, vv)) return false;
} }
@ -334,13 +334,16 @@ export function instantiate(t: Template, b: Bindings): Assertion {
return v; return v;
} }
case _Lit: case _Lit:
return Lit._.value(t); return Lit._.value(t) as Assertion;
case _TCompound: { case _TCompound: {
const ctor = TCompound._.ctor(t); const ctor = TCompound._.ctor(t);
const members = TCompound._.members(t); const members = TCompound._.members(t);
switch (ctor.label) { switch (ctor.label) {
case _CRec: { case _CRec: {
const v = Record<Assertion, any, Ref>(CRec._.label(ctor), []); const v = Record<Assertion, any, Ref>(
CRec._.label(ctor) as Assertion,
[],
);
v.length = CRec._.arity(ctor); v.length = CRec._.arity(ctor);
for (const [key, tt] of members) { for (const [key, tt] of members) {
v[key as number] = walk(tt); v[key as number] = walk(tt);
@ -358,7 +361,7 @@ export function instantiate(t: Template, b: Bindings): Assertion {
case _CDict: { case _CDict: {
const v = new Dictionary<Assertion, Ref>(); const v = new Dictionary<Assertion, Ref>();
for (const [key, tt] of members) { for (const [key, tt] of members) {
v.set(key, walk(tt)); v.set(key as Assertion, walk(tt));
} }
return v; return v;
} }

View File

@ -1,4 +1,4 @@
import { Actor, Assertion, attenuate, CRec, forwarder, Lit, Pattern, PCompound, Ref, rfilter, Turn } from './actor.js'; import { Actor, Assertion, attenuate, CRec, Lit, Pattern, PCompound, rfilter, Turn } from './actor.js';
import { Dictionary, Record } from 'preserves'; import { Dictionary, Record } from 'preserves';
import { Dataspace, Observe } from './dataspace.js'; import { Dataspace, Observe } from './dataspace.js';
import { Worker } from 'worker_threads'; import { Worker } from 'worker_threads';
@ -60,7 +60,7 @@ Turn.for(new Actor(), async (t: Turn) => {
new Dictionary()), new Dictionary()),
PCompound(CRec(Observe.constructorInfo.label, PCompound(CRec(Observe.constructorInfo.label,
Observe.constructorInfo.arity), Observe.constructorInfo.arity),
new Dictionary<Pattern, Ref>([ new Dictionary<Pattern, never>([
[0, Lit(SetBox.constructorInfo.label)]])))); [0, Lit(SetBox.constructorInfo.label)]]))));
const ds_for_client = attenuate( const ds_for_client = attenuate(
@ -70,15 +70,15 @@ Turn.for(new Actor(), async (t: Turn) => {
new Dictionary()), new Dictionary()),
PCompound(CRec(Observe.constructorInfo.label, PCompound(CRec(Observe.constructorInfo.label,
Observe.constructorInfo.arity), Observe.constructorInfo.arity),
new Dictionary<Pattern, Ref>([ new Dictionary<Pattern, never>([
[0, Lit(BoxState.constructorInfo.label)]])))); [0, Lit(BoxState.constructorInfo.label)]]))));
const boxpath = path.join(__dirname, 'box.js'); const boxpath = path.join(__dirname, 'box.js');
const clientpath = path.join(__dirname, 'client.js'); const clientpath = path.join(__dirname, 'client.js');
spawnModule(t, boxpath, [ds_for_box, 500000, 25000]); // spawnModule(t, boxpath, [ds_for_box, 500000, 25000]);
// spawnWorker(t, boxpath, [ds_for_box, 50000, 2500]); spawnWorker(t, boxpath, [ds_for_box, 50000, 2500]);
spawnModule(t, clientpath, ds_for_client); spawnModule(t, clientpath, ds_for_client);
// spawnWorker(t, clientpath, ds_for_client); // spawnWorker(t, clientpath, ds_for_client);

View File

@ -1,31 +1,76 @@
import { Handle, Ref } from 'actor'; import { Attenuation, Handle, Pattern, Ref, Template } from './actor.js';
import { Record, Value } from 'preserves'; import { Record, Value } from 'preserves';
export const _Assert = Symbol.for('assert'); export const _Assert = Symbol.for('assert');
export const Assert = Record.makeConstructor<{assertion: Value<WireRef>, handle: Handle}, WireRef>()(
_Assert, ['assertion', 'handle']);
export const _Retract = Symbol.for('retract'); export const _Retract = Symbol.for('retract');
export const Retract = Record.makeConstructor<{handle: Handle}, WireRef>()(
_Retract, ['handle']);
export const _Message = Symbol.for('message'); export const _Message = Symbol.for('message');
export const Message = Record.makeConstructor<{body: Value<WireRef>}, WireRef>()(
_Message, ['body']);
export const _Sync = Symbol.for('sync'); export const _Sync = Symbol.for('sync');
export const Sync = Record.makeConstructor<{peer: WireRef}, WireRef>()(
_Sync, ['peer']);
export type EntityMessage = function mk<T extends object>() {
| Record<typeof _Assert, [Value<WireRef>, Handle], WireRef> return {
| Record<typeof _Retract, [Handle], WireRef> Assert: Record.makeConstructor<{assertion: Value<T>, handle: Handle}, T>()(
| Record<typeof _Message, [Value<WireRef>], WireRef> _Assert, ['assertion', 'handle']),
| Record<typeof _Sync, [WireRef], WireRef>; Retract: Record.makeConstructor<{handle: Handle}, T>()(
_Retract, ['handle']),
Message: Record.makeConstructor<{body: Value<T>}, T>()(
_Message, ['body']),
Sync: Record.makeConstructor<{peer: T}, T>()(
_Sync, ['peer']),
};
}
export type TurnMessage = Array<[Oid, EntityMessage]>; export type EntityMessage<T extends object> =
| Record<typeof _Assert, [Value<T>, Handle], T>
| Record<typeof _Retract, [Handle], T>
| Record<typeof _Message, [Value<T>], T>
| Record<typeof _Sync, [T], T>;
export type TurnMessage<T extends object> = Array<[Oid, EntityMessage<T>]>;
export type Oid = number; export type Oid = number;
export type RefLocation = "mine" | "your";
export type WireRef = { loc: RefLocation, oid: Oid }; export type WireSymbol = { oid: Oid, ref: Ref, count: number };
export type WireSymbol = { name: WireRef, ref: Ref, count: number };
export type WireRef =
| { loc: "mine", oid: Oid }
| { loc: "your", oid: Oid, attenuation: EncodedAttenuation };
export const IO = mk<WireRef>();
export function myRef(oid: Oid): WireRef & { loc: "mine" } {
return { loc: 'mine', oid };
}
export function yourRef(oid: Oid, attenuation: EncodedAttenuation): WireRef & { loc: "your" } {
return { loc: 'your', oid, attenuation };
}
export type EncodedAttenuation = Array<Array<[Value<WireRef>, Value<WireRef>]>>;
export function encodeAttenuation(a: Attenuation | undefined): EncodedAttenuation {
if (a === void 0) return [];
return a.map(s => s.map(({pattern, template}) => [
pattern as Value<WireRef>,
template as Value<WireRef>,
]));
}
export function decodeAttenuation(v: Array<Value<WireRef>>): Attenuation {
function complain(): never {
throw new Error(
`Received invalid attenuation ${v.asPreservesText()} from peer`);
}
if (v.length === 0) return [];
return v.map(s => {
if (!Array.isArray(s)) complain();
return s.map(e => {
if (!(Array.isArray(e) && e.length === 2)) complain();
// TODO: check structure of pattern and template
return {
pattern: e[0] as Pattern,
template: e[1] as Template,
};
});
});
}

View File

@ -1,19 +1,21 @@
import { Actor, Assertion, Entity, Handle, Ref, Turn } from './actor.js'; import { Actor, Assertion, attenuate, Entity, Handle, Ref, Turn } from './actor.js';
import { Bytes, decode, encode, IdentityMap, mapPointers, underlying, Value } from 'preserves'; import { Bytes, canonicalString, decode, encode, FlexMap, IdentityMap, mapPointers, underlying, Value } from 'preserves';
import { import {
Oid, EncodedAttenuation,
Assert,
EntityMessage, EntityMessage,
Message, IO,
Retract, Oid,
Sync, TurnMessage,
WireRef,
WireSymbol, WireSymbol,
_Assert, _Assert,
_Retract,
_Message, _Message,
_Retract,
_Sync, _Sync,
WireRef, decodeAttenuation,
TurnMessage, encodeAttenuation,
myRef,
yourRef,
} from './protocol.js'; } from './protocol.js';
import { queueTask } from './task.js'; import { queueTask } from './task.js';
@ -50,34 +52,36 @@ export class SyncPeerEntity implements Entity {
export class RelayEntity implements Entity { export class RelayEntity implements Entity {
readonly relay: Relay; readonly relay: Relay;
readonly e: WireSymbol; readonly oid: Oid;
constructor(relay: Relay, e: WireSymbol) { constructor(relay: Relay, oid: Oid) {
this.relay = relay; this.relay = relay;
this.e = e; this.oid = oid;
} }
send(m: EntityMessage): void { send(m: EntityMessage<WireRef>): void {
this.relay.send(this.e.name.oid, m); this.relay.send(this.oid, m);
} }
assert(_turn: Turn, assertion: Assertion, handle: Handle): void { assert(_turn: Turn, assertion: Assertion, handle: Handle): void {
this.send(Assert(this.relay.register(assertion, handle), handle)); this.send(IO.Assert(this.relay.register(assertion, handle), handle));
} }
retract(_turn: Turn, handle: Handle): void { retract(_turn: Turn, handle: Handle): void {
this.relay.deregister(handle); this.relay.deregister(handle);
this.send(Retract(handle)); this.send(IO.Retract(handle));
} }
message(_turn: Turn, body: Assertion): void { message(_turn: Turn, body: Assertion): void {
this.send(Message(this.relay.register(body, null))); this.send(IO.Message(this.relay.register(body, null)));
} }
sync(turn: Turn, peer: Ref): void { sync(turn: Turn, peer: Ref): void {
const peerEntity = new SyncPeerEntity(this.relay, peer); const peerEntity = new SyncPeerEntity(this.relay, peer);
peerEntity.e = this.relay.rewriteRefOut(turn.ref(peerEntity), false, null); const exported: Array<WireSymbol> = [];
this.send(Sync(peerEntity.e.name)); const ior = this.relay.rewriteRefOut(turn.ref(peerEntity), false, exported);
peerEntity.e = exported[0];
this.send(IO.Sync(ior));
} }
} }
@ -86,17 +90,15 @@ export class Membrane {
readonly byRef = new IdentityMap<Ref, WireSymbol>(); readonly byRef = new IdentityMap<Ref, WireSymbol>();
grab<Table extends "byOid" | "byRef">(table: Table, grab<Table extends "byOid" | "byRef">(table: Table,
key: (Table extends "byOid" ? Oid : Ref), key: Parameters<Membrane[Table]['get']>[0],
transient: boolean, transient: boolean,
f: () => WireSymbol): WireSymbol f: () => WireSymbol): WireSymbol
{ {
let e = let e = this[table].get(key as any);
(this[table] as IdentityMap<Table extends "byOid" ? Oid : Ref, WireSymbol>)
.get(key);
if (e === void 0) { if (e === void 0) {
e = f(); e = f();
this.byRef.set(e.ref, e); this.byRef.set(e.ref, e);
this.byOid.set(e.name.oid, e); this.byOid.set(e.oid, e);
} }
if (!transient) e.count++; if (!transient) e.count++;
return e; return e;
@ -105,7 +107,7 @@ export class Membrane {
drop(e: WireSymbol): void { drop(e: WireSymbol): void {
e.count--; e.count--;
if (e.count === 0) { if (e.count === 0) {
this.byOid.delete(e.name.oid); this.byOid.delete(e.oid);
this.byRef.delete(e.ref); this.byRef.delete(e.ref);
} }
} }
@ -126,9 +128,9 @@ export interface RelayOptions {
packetWriter: PacketWriter; packetWriter: PacketWriter;
setup(t: Turn, r: Relay): void; setup(t: Turn, r: Relay): void;
debug?: boolean; debug?: boolean;
trustPeer?: boolean;
} }
export class Relay { export class Relay {
readonly actor: Actor; readonly actor: Actor;
readonly w: PacketWriter; readonly w: PacketWriter;
@ -140,21 +142,22 @@ export class Relay {
readonly exported = new Membrane(); readonly exported = new Membrane();
readonly imported = new Membrane(); readonly imported = new Membrane();
nextLocalOid: Oid = 0; nextLocalOid: Oid = 0;
pendingTurn: TurnMessage = []; pendingTurn: TurnMessage<WireRef> = [];
debug: boolean; debug: boolean;
trustPeer: boolean;
constructor(t: Turn, options: RelayOptions) { constructor(t: Turn, options: RelayOptions) {
this.actor = t.actor; this.actor = t.actor;
this.w = options.packetWriter; this.w = options.packetWriter;
this.debug = options.debug ?? false; this.debug = options.debug ?? false;
this.trustPeer = options.trustPeer ?? true;
options.setup(t, this); options.setup(t, this);
} }
rewriteOut(assertion: Assertion, transient: boolean): [Value<WireRef>, Array<WireSymbol>] rewriteOut(assertion: Assertion, transient: boolean): [Value<WireRef>, Array<WireSymbol>]
{ {
const exported: Array<WireSymbol> = []; const exported: Array<WireSymbol> = [];
const rewritten = const rewritten = mapPointers(assertion, r => this.rewriteRefOut(r, transient, exported));
mapPointers(assertion, r => this.rewriteRefOut(r, transient, exported).name);
return [rewritten, exported]; return [rewritten, exported];
} }
@ -175,44 +178,61 @@ export class Relay {
(this.outboundAssertions.get(handle) ?? []).forEach(e => this.releaseRefOut(e)); (this.outboundAssertions.get(handle) ?? []).forEach(e => this.releaseRefOut(e));
} }
rewriteRefOut(r: Ref, transient: boolean, exported: Array<WireSymbol> | null): WireSymbol { rewriteRefOut(r: Ref, transient: boolean, exported: Array<WireSymbol>): WireRef {
if (r.target instanceof RelayEntity && r.target.relay === this) { if (r.target instanceof RelayEntity && r.target.relay === this) {
return r.target.e; if (r.attenuation === void 0 || r.attenuation.length === 0) {
} else { // No extra conditions on this reference since it was sent to us.
const e = this.exported.grab("byRef", r, transient, () => { return yourRef(r.target.oid, []);
if (transient) throw new Error("Cannot send transient reference"); } else {
return { name: { loc: "mine", oid: this.nextLocalOid++ }, ref: r, count: 0 }; // This reference has been attenuated since it was sent to us.
}); // Do we trust the peer to enforce such attenuation on our behalf?
exported?.push(e); if (this.trustPeer) {
return e; return yourRef(r.target.oid, encodeAttenuation(r.attenuation));
} else {
// fall through: treat the attenuated ref as a local ref, and re-export it.
}
}
} }
const e = this.exported.grab(
"byRef", r, transient, () => {
if (transient) throw new Error("Cannot send transient reference");
return { oid: this.nextLocalOid++, ref: r, count: 0 };
});
exported.push(e);
return myRef(e.oid);
} }
releaseRefOut(e: WireSymbol) { releaseRefOut(e: WireSymbol) {
this.exported.drop(e); this.exported.drop(e);
} }
rewriteRefIn(t: Turn, n: WireRef, imported: Array<WireSymbol> | null): Ref { rewriteRefIn(t: Turn, n: WireRef, imported: Array<WireSymbol>): Ref {
switch (n.loc) { switch (n.loc) {
case 'your': case 'your': {
return this.lookupLocal(n.oid); const r = this.lookupLocal(n.oid);
if (n.attenuation.length === 0 || r === INERT_REF) {
return r;
} else {
type AttenuatedRef = Ref & { __attenuations?: FlexMap<EncodedAttenuation, Ref> };
const ar = r as AttenuatedRef;
if (ar.__attenuations === void 0) {
ar.__attenuations = new FlexMap(canonicalString);
}
return ar.__attenuations.getOrSet(n.attenuation, () =>
attenuate(r, ... decodeAttenuation(n.attenuation)));
}
}
case 'mine': { case 'mine': {
const e = this.imported.grab("byOid", n.oid, false, () => { const e = this.imported.grab("byOid", n.oid, false, () =>
const e: WireSymbol = { ({ oid: n.oid, ref: t.ref(new RelayEntity(this, n.oid)), count: 0 }));
name: { loc: 'your', oid: n.oid }, imported.push(e);
ref: null as any,
count: 0,
};
e.ref = t.ref(new RelayEntity(this, e as WireSymbol));
return e;
});
imported?.push(e);
return e.ref; return e.ref;
} }
} }
} }
send(remoteOid: Oid, m: EntityMessage): void { send(remoteOid: Oid, m: EntityMessage<WireRef>): void {
if (this.pendingTurn.length === 0) { if (this.pendingTurn.length === 0) {
queueTask(() => { queueTask(() => {
if (this.debug) console.log('OUT', this.pendingTurn.asPreservesText()); if (this.debug) console.log('OUT', this.pendingTurn.asPreservesText());
@ -221,7 +241,7 @@ export class Relay {
encodePointer: n => { encodePointer: n => {
switch (n.loc) { switch (n.loc) {
case 'mine': return [0, n.oid]; case 'mine': return [0, n.oid];
case 'your': return [1, n.oid]; case 'your': return [1, n.oid, ... n.attenuation];
} }
}, },
}))); })));
@ -240,40 +260,47 @@ export class Relay {
const bs = Bytes.from(bs0); const bs = Bytes.from(bs0);
const wireTurn = decode<WireRef>(bs, { const wireTurn = decode<WireRef>(bs, {
decodePointer: v => { decodePointer: v => {
if (!Array.isArray(v) || v.length !== 2) { function complain(): never {
throw new Error( throw new Error(
`Received invalid object reference ${v.asPreservesText()} from peer`); `Received invalid object reference ${v.asPreservesText()} from peer`);
} }
const loc = v[0] === 0 ? 'mine' : 'your'; if (!(Array.isArray(v) && v.length >= 2 && typeof v[1] === 'number')) {
return { loc, oid: v[1] as Oid }; complain();
}
const oid = v[1] as Oid;
switch (v[0]) {
case 0:
if (v.length > 2) complain();
return myRef(oid);
case 1:
// TODO: check EncodedAttenuation
return yourRef(oid, v.slice(2) as EncodedAttenuation);
default:
complain();
}
}, },
}); }) as TurnMessage<WireRef>;
// ^ TODO: deep check that v is a TurnMessage
if (this.debug) console.log('IN', wireTurn.asPreservesText()); if (this.debug) console.log('IN', wireTurn.asPreservesText());
if (!Array.isArray(wireTurn)) invalidTopLevelMessage(wireTurn);
wireTurn.forEach(v => { wireTurn.forEach(v => {
if (Array.isArray(v) && v.length === 2 && typeof v[0] === 'number') { const [localOid, m] = v;
const [localOid, m] = v; this.handle(t, this.lookupLocal(localOid), m);
// TODO: deep check that m is EntityMessage
this.handle(t, this.lookupLocal(localOid), m as EntityMessage);
} else {
invalidTopLevelMessage(wireTurn);
}
}); });
}); });
} }
handle(t: Turn, r: Ref, m: EntityMessage) { handle(t: Turn, r: Ref, m: EntityMessage<WireRef>) {
switch (m.label) { switch (m.label) {
case _Assert: { case _Assert: {
const [a, imported] = this.rewriteIn(t, Assert._.assertion(m)); const [a, imported] = this.rewriteIn(t, IO.Assert._.assertion(m));
this.inboundAssertions.set(Assert._.handle(m), { this.inboundAssertions.set(IO.Assert._.handle(m), {
localHandle: t.assert(r, a), localHandle: t.assert(r, a),
imported, imported,
}); });
break; break;
} }
case _Retract: { case _Retract: {
const remoteHandle = Retract._.handle(m); const remoteHandle = IO.Retract._.handle(m);
const h = this.inboundAssertions.get(remoteHandle); const h = this.inboundAssertions.get(remoteHandle);
if (h === void 0) throw new Error(`Peer retracted invalid handle ${remoteHandle}`); if (h === void 0) throw new Error(`Peer retracted invalid handle ${remoteHandle}`);
this.inboundAssertions.delete(remoteHandle); this.inboundAssertions.delete(remoteHandle);
@ -282,14 +309,14 @@ export class Relay {
break; break;
} }
case _Message: { case _Message: {
const [a, imported] = this.rewriteIn(t, Message._.body(m)); const [a, imported] = this.rewriteIn(t, IO.Message._.body(m));
if (imported.length > 0) throw new Error("Cannot receive transient reference"); if (imported.length > 0) throw new Error("Cannot receive transient reference");
t.message(r, a); t.message(r, a);
break; break;
} }
case _Sync: { case _Sync: {
const imported: Array<WireSymbol> = []; const imported: Array<WireSymbol> = [];
const k = this.rewriteRefIn(t, Sync._.peer(m), imported); const k = this.rewriteRefIn(t, IO.Sync._.peer(m), imported);
t.sync(r).then(t => { t.sync(r).then(t => {
t.message(k, true); t.message(k, true);
imported.forEach(e => this.imported.drop(e)); imported.forEach(e => this.imported.drop(e));
@ -300,11 +327,6 @@ export class Relay {
} }
} }
function invalidTopLevelMessage(m: Value<WireRef>): never {
throw new Error(
`Received invalid top-level protocol message from peer: ${m.asPreservesText()}`);
}
export interface RelayActorOptions extends RelayOptions { export interface RelayActorOptions extends RelayOptions {
initialOid?: Oid; initialOid?: Oid;
initialRef?: Ref; initialRef?: Ref;
@ -316,10 +338,10 @@ export function spawnRelay(t: Turn, options: RelayActorOptions): Promise<Ref | n
t.spawn(t => { t.spawn(t => {
const relay = new Relay(t, options); const relay = new Relay(t, options);
if (options.initialRef !== void 0) { if (options.initialRef !== void 0) {
relay.rewriteRefOut(options.initialRef, false, null); relay.rewriteRefOut(options.initialRef, false, []);
} }
if (options.initialOid !== void 0) { if (options.initialOid !== void 0) {
resolve(relay.rewriteRefIn(t, { loc: 'mine', oid: options.initialOid }, null)); resolve(relay.rewriteRefIn(t, myRef(options.initialOid), []));
} else { } else {
resolve(null); resolve(null);
} }