Skip to content

Commit

Permalink
add captp experiment
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Oct 12, 2019
1 parent d321ef8 commit c7f222f
Show file tree
Hide file tree
Showing 6 changed files with 2,255 additions and 59 deletions.
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# New Repo
# `@agoric/mini-captp`

This repository should be a compilation of everything that a new
Agoric repo should have, including ESLint settings, prettier settings,
package.json dependencies and scripts, licenses, sample tests, CircleCI config, and
VSCode testing config.
A miniature CapTP implementation leveraging Agoric's published modules.

See [SETUP-DELETEME](SETUP-DELETEME.md) for starting steps.
## Usage

```
import makeCapTP from '@agoric/mini-captp';
import { E } from '@agoric/eventual-send';
// Create a message handler and bootstrap.
// Messages on myconn are exchanged with JSON-able objects.
const [handler, getBootstrap] = makeCapTP('myid', myconn.send, myBootstrap);
myconn.onReceive = obj => handler[obj.type](obj);
// Get the remote's bootstrap object and call a remote method.
E(getBootstrap()).method(args).then(res => console.log('got res', res));
```
192 changes: 192 additions & 0 deletions lib/captp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// This logic was mostly lifted from @agoric/swingset-vat liveSlots.js
// Defects in it are mfig's fault.
import { makeMarshal, QCLASS } from '@agoric/marshal';
import harden from '@agoric/harden';
import Nat from '@agoric/nat';
import { HandledPromise } from '@agoric/eventual-send';

export default function makeCapTP(ourId, send, bootstrapObj = undefined) {
const { serialize, unserialize } = makeMarshal(
// eslint-disable-next-line no-use-before-define
serializeSlot,
// eslint-disable-next-line no-use-before-define
unserializeSlot,
);

let lastPromiseID = 0;
let lastExportID = 0;
let lastQuestionID = 0;

const valToSlot = new WeakMap();
const slotToVal = new Map(); // exports, answers
const questions = new Map(); // chosen by us
const imports = new Map(); // chosen by our peer

function serializeSlot(val, slots, slotMap) {
if (!slotMap.has(val)) {
let slot;
if (!valToSlot.has(val)) {
// new export
if (Promise.resolve(val) === val) {
lastPromiseID += 1;
slot = `p${lastPromiseID}`;
val.then(
res =>
send({
type: 'CTP_RESOLVE',
promiseID: lastPromiseID,
res: serialize(harden(res)),
}),
rej =>
send({
type: 'CTP_RESOLVE',
promiseID: lastPromiseID,
rej: serialize(harden(rej)),
}),
);
} else {
lastExportID += 1;
const exportID = lastExportID;
slot = `o${exportID}`;
}
valToSlot.set(val, slot);
slotToVal.set(slot, val);
}

slot = valToSlot.get(val);
const slotIndex = slots.length;
slots.push(slot);
slotMap.set(val, slotIndex);
}

const slotIndex = slotMap.get(val);
return harden({
[QCLASS]: 'slot',
index: slotIndex,
});
}

function unserializeSlot(data, slots) {
const slot = slots[Nat(data.index)];
let val;
if (!slotToVal.has(slot)) {
// Make a new handled promise for the slot.
const handler = {
POST(_o, prop, args) {
// Support: o~.[prop](...args) remote method invocation
const pr = {};
pr.p = new Promise((resolve, reject) => {
pr.res = resolve;
pr.rej = reject;
});
lastQuestionID += 1;
questions.set(lastQuestionID, pr);
send({
type: 'CTP_CALL',
questionID: lastQuestionID,
target: slot,
method: serialize(harden([prop, args])),
});
return harden(pr.p);
},
};

const pr = {};
pr.p = Promise.makeHandled((res, rej, resolveWithPresence) => {
pr.rej = rej;
pr.resPres = () => resolveWithPresence(handler);
pr.res = res;
}, handler);
harden(pr);

if (slot[0] === 'o') {
// A new presence
const presence = pr.resPres();
presence.toString = () => `[Presence ${ourId} ${slot}]`;
harden(presence);
val = presence;
} else {
// A new promise
imports.set(Number(slot.slice(1)), pr);
val = pr.p;
}
slotToVal.set(slot, val);
valToSlot.set(val, slot);
}
return slotToVal.get(slot);
}

const handler = {
CTP_BOOTSTRAP(obj) {
const { questionID } = obj;
send({
type: 'CTP_RETURN',
answerID: questionID,
result: serialize(bootstrapObj),
});
},
CTP_CALL(obj) {
const { questionID, target } = obj;
const [prop, args] = unserialize(obj.method);
const val = unserialize({
body: JSON.stringify({
[QCLASS]: 'slot',
index: 0,
}),
slots: [target],
});
HandledPromise.applyMethod(val, prop, args)
.then(res =>
send({
type: 'CTP_RETURN',
answerID: questionID,
result: serialize(harden(res)),
}),
)
.catch(rej =>
send({
type: 'CTP_RETURN',
answerID: questionID,
exception: serialize(harden(rej)),
}),
);
},
CTP_RETURN(obj) {
const { result, exception, answerID } = obj;
const pr = questions.get(answerID);
if ('exception' in obj) {
pr.rej(unserialize(exception));
} else {
pr.res(unserialize(result));
}
questions.delete(answerID);
},
CTP_RESOLVE(obj) {
const { promiseID, res, rej } = obj;
const pr = imports.get(promiseID);
if ('rej' in obj) {
pr.rej(unserialize(rej));
} else {
pr.res(unserialize(res));
}
imports.delete(promiseID);
},
};

// Get a reference to the other side's bootstrap object.
const getBootstrap = () => {
const pr = {};
pr.p = new Promise((resolve, reject) => {
pr.res = resolve;
pr.rej = reject;
});
lastQuestionID += 1;
questions.set(lastQuestionID, pr);
send({
type: 'CTP_BOOTSTRAP',
questionID: lastQuestionID,
});
return harden(pr.p);
};
return [handler, getBootstrap];
}
Loading

0 comments on commit c7f222f

Please sign in to comment.