From 61174815eb348d1d7b8796a15d908851306e808b Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 19 Apr 2021 13:00:08 +0200 Subject: [PATCH] Better SturdyRef binding and lookup --- schemas/sturdy.prs | 1 + src/dataspace.ts | 37 ++++++++++++++++---- src/gen/sturdy.ts | 34 ++++++++++++++++++ src/sandbox.ts | 10 +++--- src/secure-chat-client.ts | 4 +-- src/secure-chat-moderator.ts | 19 +++++++--- src/server.ts | 67 +++++++++++++++++++----------------- src/simple-chat.ts | 2 +- 8 files changed, 124 insertions(+), 50 deletions(-) diff --git a/schemas/sturdy.prs b/schemas/sturdy.prs index 2c69c11..9a63d5d 100644 --- a/schemas/sturdy.prs +++ b/schemas/sturdy.prs @@ -16,6 +16,7 @@ Rewrite = . Alts = . Resolve = . +Bind = . ;--------------------------------------------------------------------------- diff --git a/src/dataspace.ts b/src/dataspace.ts index b514eda..e02a620 100644 --- a/src/dataspace.ts +++ b/src/dataspace.ts @@ -1,5 +1,5 @@ import { Assertion, Entity, Handle, LocalAction, Ref, Turn } from 'actor'; -import { Dictionary, IdentityMap, is, preserves, Record, Tuple } from '@preserves/core'; +import { Dictionary, IdentityMap, is, Record, Tuple } from '@preserves/core'; import { Bag, ChangeDescription } from './bag'; import { fromObserve, toObserve, Observe } from './gen/dataspace'; @@ -87,16 +87,39 @@ export class Dataspace implements Partial { } } -export function during(f: (t: Turn, a: Assertion) => (LocalAction | null)): Partial { - const assertionMap = new Map(); +export function during(f: (t: Turn, a: Assertion) => Promise): Partial { + const assertionMap = new Map(); return { assert(t: Turn, a: Assertion, h: Handle): void { - const g = f(t, a); - if (g !== null) assertionMap.set(h, g); + f(t, a).then(g => { + if (g === null) g = _t => {}; + switch (assertionMap.get(h)) { + case void 0: + assertionMap.set(h, g); + break; + case 'dead': + assertionMap.delete(h); + t.freshen(g); + break; + default: + console.error('during: Duplicate handle in assert: ' + h); + break; + } + }); }, retract(t: Turn, h: Handle): void { - assertionMap.get(h)?.(t); - assertionMap.delete(h); + const g = assertionMap.get(h); + switch (g) { + case void 0: + assertionMap.set(h, 'dead'); + break; + case 'dead': + console.error('during: Duplicate handle in retract: ' + h); + break; + default: + assertionMap.delete(h); + g(t); + } }, }; } diff --git a/src/gen/sturdy.ts b/src/gen/sturdy.ts index 9148abd..946d224 100644 --- a/src/gen/sturdy.ts +++ b/src/gen/sturdy.ts @@ -42,6 +42,8 @@ export type Alts = {"alternatives": Array}; export type Resolve = {"sturdyref": SturdyRef, "observer": _ptr}; +export type Bind = {"oid": _val, "key": _.Bytes, "target": _ptr}; + export type ConstructorSpec = ( {"_variant": "CRec", "value": CRec} | {"_variant": "CArr", "value": CArr} | @@ -123,6 +125,8 @@ export function Alts(alternatives: Array): Alts {return {"alternatives" export function Resolve({sturdyref, observer}: {sturdyref: SturdyRef, observer: _ptr}): Resolve {return {"sturdyref": sturdyref, "observer": observer};} +export function Bind({oid, key, target}: {oid: _val, key: _.Bytes, target: _ptr}): Bind {return {"oid": oid, "key": key, "target": target};} + export namespace ConstructorSpec { export function CRec(value: CRec): ConstructorSpec {return {"_variant": "CRec", "value": value};}; export function CArr(value: CArr): ConstructorSpec {return {"_variant": "CArr", "value": value};}; @@ -369,6 +373,36 @@ export function toResolve(v: _val): undefined | Resolve { export function fromResolve(_v: Resolve): _val {return _.Record($resolve, [fromSturdyRef(_v["sturdyref"]), _v["observer"]]);} +export function asBind(v: _val): Bind { + let result = toBind(v); + if (result === void 0) throw new TypeError(`Invalid Bind: ${_.stringify(v)}`); + return result; +} + +export function toBind(v: _val): undefined | Bind { + let result: undefined | Bind; + if (_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v)) { + let _tmp0: (null) | undefined; + _tmp0 = _.is(v.label, $bind) ? null : void 0; + if (_tmp0 !== void 0) { + let _tmp1: (_val) | undefined; + _tmp1 = v[0]; + if (_tmp1 !== void 0) { + let _tmp2: (_.Bytes) | undefined; + _tmp2 = _.Bytes.isBytes(v[1]) ? v[1] : void 0; + if (_tmp2 !== void 0) { + let _tmp3: (_ptr) | undefined; + _tmp3 = _toPtr(v[2]); + if (_tmp3 !== void 0) {result = {"oid": _tmp1, "key": _tmp2, "target": _tmp3};}; + }; + }; + }; + }; + return result; +} + +export function fromBind(_v: Bind): _val {return _.Record($bind, [_v["oid"], _v["key"], _v["target"]]);} + export function asConstructorSpec(v: _val): ConstructorSpec { let result = toConstructorSpec(v); if (result === void 0) throw new TypeError(`Invalid ConstructorSpec: ${_.stringify(v)}`); diff --git a/src/sandbox.ts b/src/sandbox.ts index 7668576..d28c183 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -2,6 +2,7 @@ import { Actor, Ref, Turn } from "./actor"; import { Relay, spawnRelay } from "./relay"; import { sturdyDecode } from "./sturdy"; import { Resolve, asSturdyRef, fromResolve } from "./gen/sturdy"; +import { during } from "./dataspace"; import * as net from 'net'; import { Bytes } from "@preserves/core"; @@ -37,11 +38,10 @@ const socket = net.createConnection({ port: 5999, host: 'localhost' }, () => { t.assert(shutdownRef, true); t.assert(gatekeeper, fromResolve(Resolve({ sturdyref: asSturdyRef(cap), - observer: t.ref({ - assert(t, ds) { - m.default(t, ds); - } - }) + observer: t.ref(during(async (t, ds) => { + const facet = t.facet(t => m.default(t, ds)); + return t => t.stop(facet); + })), }))); }))); }); diff --git a/src/secure-chat-client.ts b/src/secure-chat-client.ts index d0d1008..2b0ae5b 100644 --- a/src/secure-chat-client.ts +++ b/src/secure-chat-client.ts @@ -4,7 +4,7 @@ import { Assertion, Ref, Turn } from "./actor.js"; import { attachReadline } from './readline.js'; export default function (t: Turn, ds: Ref) { - observe(t, ds, $joinedUser, during((t, j0) => { + observe(t, ds, $joinedUser, during(async (t, j0) => { const j = asJoin(j0); const facet = t.facet(t => runSession(t, j.uid, j.handle)); return t => t.stop(facet); @@ -37,7 +37,7 @@ function runSession(t: Turn, uid: UserId, session: Ref) { updateUsername(t, 'user' + process.pid); const users = new Map(); - observe(t, session, $user, during((_t, ui0) => { + observe(t, session, $user, during(async (_t, ui0) => { const ui = asUserInfo(ui0); const oldName = users.get(ui.uid); console.log(oldName === void 0 diff --git a/src/secure-chat-moderator.ts b/src/secure-chat-moderator.ts index b7171cf..dbf4cf6 100644 --- a/src/secure-chat-moderator.ts +++ b/src/secure-chat-moderator.ts @@ -13,17 +13,28 @@ import { fromUserInfo, } from "./gen/secure-chat-protocol.js"; import { Assertion, Handle, Ref, Turn } from "./actor.js"; -import { observe, during, $Observe, asObserve } from "./dataspace.js"; +import { observe, during, $Observe, asObserve, Dataspace } from "./dataspace.js"; import { attenuate, rfilter, pRec, pPointer, pString, pLit } from "./rewrite.js"; +import { attenuate as sturdyAttenuate, fromBind, Bind, KEY_LENGTH, sturdyEncode, fromSturdyRef, mint } from "./sturdy.js"; +import { Bytes } from "@preserves/core"; -export default function (t: Turn, ds: Ref) { +export default function (t: Turn, gatekeeperDs: Ref) { let nextUserId: UserId = 0; - // TODO: print out limited authority, only allowed to observe $joinedUser + const ds = t.ref(new Dataspace()); + + const chatOid = 'chat'; + const chatKey = new Bytes(KEY_LENGTH); + t.assert(gatekeeperDs, fromBind(Bind({ oid: chatOid, key: chatKey, target: ds }))); + mint(chatOid, chatKey).then(async r => { + r = await sturdyAttenuate(r, rfilter(pRec($Observe, pLit($joinedUser), pPointer()))); + console.log(fromSturdyRef(r).asPreservesText()); + console.log(sturdyEncode(fromSturdyRef(r)).toHex()); + }); const nicks = new Map(); - observe(t, ds, $Observe, during((t, o0) => { + observe(t, ds, $Observe, during(async (t, o0) => { const o = asObserve(o0); if (o.label !== $joinedUser) return null; diff --git a/src/server.ts b/src/server.ts index 45e9ae0..4bd3eb4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,24 +1,31 @@ import { Actor, Handle, Turn } from './actor.js'; -import { Dataspace } from './dataspace.js'; +import { Dataspace, during, observe } from './dataspace.js'; import { Relay, spawnRelay } from './relay.js'; import * as net from 'net'; import { mint, sturdyEncode, validate } from './sturdy.js'; import { KEY_LENGTH } from './cryptography.js'; import { attenuate } from './rewrite.js'; -import { Bytes, IdentityMap } from '@preserves/core'; -import { Attenuation, fromSturdyRef, toResolve } from './gen/sturdy.js'; - -const secretKey = new Bytes(KEY_LENGTH); -mint('syndicate', secretKey).then(v => { - console.log(fromSturdyRef(v).asPreservesText()); - console.log(sturdyEncode(fromSturdyRef(v)).toHex()); -}); +import { Bytes, is } from '@preserves/core'; +import { $bind, Attenuation, Bind, fromBind, fromSturdyRef, toBind, toResolve, _val } from './gen/sturdy.js'; new Actor(t => { t.activeFacet.preventInertCheck(); + const ds = t.ref(new Dataspace()); + const dsOid = 'syndicate'; + const dsKey = new Bytes(KEY_LENGTH); + t.assert(ds, fromBind(Bind({ + oid: dsOid, + key: dsKey, + target: ds, + }))); + mint(dsOid, dsKey).then(v => { + console.log(fromSturdyRef(v).asPreservesText()); + console.log(sturdyEncode(fromSturdyRef(v)).toHex()); + }); + function spawnConnection(t: Turn, socket: net.Socket) { console.log('connection', socket.remoteAddress, socket.remotePort); spawnRelay(t, { @@ -31,28 +38,26 @@ new Actor(t => { socket.on('data', data => r.accept(data)); t.activeFacet.actor.atExit(() => socket.destroy()); }, - initialRef: t.ref({ - handleMap: new IdentityMap(), - async assert(t, a0, h) { - const a = toResolve(a0); - if (a === void 0) return; - const r = a.sturdyref; - if (!await validate(r, secretKey)) { - console.warn(`Invalid SturdyRef: ${r.asPreservesText()}`); - return; - } - const cavs: Attenuation = []; - r.caveatChain.forEach(cs => cavs.push(... cs)); - const attenuated_ds = attenuate(ds, ... cavs); - t.freshen(t => this.handleMap.set( - h, - t.assert(a.observer, attenuated_ds))); - }, - retract(t, h) { - t.retract(this.handleMap.get(h)); - this.handleMap.delete(h); - } - }), + initialRef: t.ref(during(async (t, a0) => { + const a = toResolve(a0); + if (a === void 0) return null; + const r = a.sturdyref; + let facet = t.facet(t => { + observe(t, ds, $bind, during(async (t, b0) => { + const b = toBind(b0); + if (b === void 0) return null; + if (!is(r.oid, b.oid)) return null; + if (!await validate(r, b.key)) return null; + const cavs: Attenuation = []; + r.caveatChain.forEach(cs => cavs.push(... cs)); + const attenuated_ds = attenuate(b.target, ... cavs); + let replyHandle: Handle | undefined; + t.freshen(t => replyHandle = t.assert(a.observer, attenuated_ds)); + return t => t.retract(replyHandle); + })); + }); + return t => t.stop(facet); + })), // debug: true, }); } diff --git a/src/simple-chat.ts b/src/simple-chat.ts index 7ca746f..fae1d1f 100644 --- a/src/simple-chat.ts +++ b/src/simple-chat.ts @@ -13,7 +13,7 @@ export default function (t: Turn, ds: Ref) { } updateUsername(t, 'user' + process.pid); - observe(t, ds, $Present, during((_t, e0) => { + observe(t, ds, $Present, during(async (_t, e0) => { const e = asPresent(e0); console.log(`${e.username} arrived`); return (_t) => console.log(`${e.username} departed`);