Cross-Relay attenuation
This commit is contained in:
parent
1cf0d9f89f
commit
94831bd140
57
src/actor.ts
57
src/actor.ts
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
10
src/main.ts
10
src/main.ts
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
180
src/relay.ts
180
src/relay.ts
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue