New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@agoric/swingset-vat

Package Overview
Dependencies
Maintainers
3
Versions
2767
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agoric/swingset-vat - npm Package Compare versions

Comparing version 0.0.3 to 0.0.4

src/devices/channel-src.js

5

package.json
{
"name": "@agoric/swingset-vat",
"version": "0.0.3",
"version": "0.0.4",
"description": "Vat/Container Launcher",

@@ -18,3 +18,3 @@ "main": "src/main.js",

"publish": "npm run-script pack && npm publish --access public",
"test": "npm run-script build-kernel && tape -r esm test/test-node-version.js && tape -r esm test/**/test*.js",
"test": "npm run-script build-kernel && tape -r esm test/test-node-version.js && tape -r esm 'test/**/test*.js' | tap-spec",
"pretty-fix": "prettier --write '**/*.{js,jsx}'",

@@ -36,2 +36,3 @@ "pretty-check": "prettier --check '**/*.{js,jsx}'",

"rollup-plugin-node-resolve": "^4.0.0",
"tap-spec": "^5.0.0",
"tape": "^4.10.0",

@@ -38,0 +39,0 @@ "tape-promise": "^4.0.0"

68

src/controller.js

@@ -21,12 +21,9 @@ /* global setImmediate */

}
if (dirent.name.startsWith('vat-')) {
let name;
let indexJS;
if (dirent.isFile() && dirent.name.endsWith('.js')) {
name = dirent.name.slice('vat-'.length, -'.js'.length);
indexJS = path.resolve(basedir, dirent.name);
} else if (dirent.isDirectory()) {
name = dirent.name.slice('vat-'.length);
indexJS = path.resolve(basedir, dirent.name, 'index.js');
}
if (
dirent.name.startsWith('vat-') &&
dirent.isFile() &&
dirent.name.endsWith('.js')
) {
const name = dirent.name.slice('vat-'.length, -'.js'.length);
const indexJS = path.resolve(basedir, dirent.name);
vatSources.set(name, indexJS);

@@ -49,3 +46,3 @@ }

}
return harden({ vatSources, bootstrapIndexJS, state });
return { vatSources, bootstrapIndexJS, state };
}

@@ -97,3 +94,3 @@

async function addVat(vatID, sourceIndex, options) {
async function addVat(vatID, sourceIndex, _options) {
if (!(sourceIndex[0] === '.' || path.isAbsolute(sourceIndex))) {

@@ -110,6 +107,2 @@ throw Error(

let setup;
// note: the contents of this 'devices' object are kernel-realm, but
// the object itself is host-realm. kernel.addVat is responsible for
// unpacking it safely and not exposing the container to vat code.
const devices = {};

@@ -128,24 +121,28 @@ if (withSES) {

setup = s.evaluate(source, { require: r })();
if (options.devices) {
Object.getOwnPropertyNames(options.devices).forEach(name => {
const d = options.devices[name];
devices[name] = s.evaluate(`(${d.attenuatorSource})`, { require: r })(
d,
);
});
}
} else {
// eslint-disable-next-line global-require,import/no-dynamic-require
setup = require(`${sourceIndex}`).default;
if (options.devices) {
Object.getOwnPropertyNames(options.devices).forEach(name => {
const d = options.devices[name];
// eslint-disable-next-line no-eval
devices[name] = eval(d.attenuatorSource)(d);
});
}
}
kernel.addVat(vatID, setup, devices);
kernel.addVat(vatID, setup);
}
async function addDevice(name, sourceIndex, endowments) {
if (!(sourceIndex[0] === '.' || path.isAbsolute(sourceIndex))) {
throw Error(
'sourceIndex must be relative (./foo) or absolute (/foo) not bare (foo)',
);
}
let setup;
if (withSES) {
let source = await bundleSource(`${sourceIndex}`);
source = `(${source})`;
setup = s.evaluate(source, { require: r })();
} else {
// eslint-disable-next-line global-require,import/no-dynamic-require
setup = require(`${sourceIndex}`).default;
}
kernel.addDevice(name, setup, endowments);
}
// the kernel won't leak our objects into the Vats, we must do

@@ -188,2 +185,9 @@ // the same in this wrapper

if (config.devices) {
for (const [name, srcpath, endowments] of config.devices) {
// eslint-disable-next-line no-await-in-loop
await addDevice(name, srcpath, endowments);
}
}
if (config.vatSources) {

@@ -190,0 +194,0 @@ for (const vatID of config.vatSources.keys()) {

import harden from '@agoric/harden';
import Nat from '@agoric/nat';
import { makeLiveSlots } from './liveSlots';
import { makeDeviceSlots } from './deviceSlots';
import { makeCommsSlots } from './commsSlots/index';
import { QCLASS, makeMarshal } from './marshal';
import makePromise from './makePromise';
import makeVatManager from './vatManager';
import makeDeviceManager from './deviceManager';

@@ -21,27 +24,8 @@ export default function buildKernel(kernelEndowments) {

// 'id' is always a non-negative integer (a Nat)
// 'slot' is a type(string)+id, plus maybe a vatid (string)
// * 'relative slot' lacks a vatid because it is implicit
// used in syscall.send and dispatch.deliver
// * 'absolute slot' includes an explicit vatid
// used in kernel's runQueue
// 'slots' is an array of 'slot', in send/deliver
// 'slotIndex' is an index into 'slots', used in serialized JSON
// key = `${type}.${toVatID}.${slotID}`
// vats[vatid] = { imports: { inbound[key] = importID,
// outbound[importID] = slot, },
// nextImportID,
// promises: { inbound[kernelPromiseID] = id,
// outbound[id] = kernelPromiseID, },
// nextPromiseID,
// resolvers: { inbound[kernelPromiseID] = id,
// outbound[id] = kernelPromiseID, },
// nextResolverID,
// }
const vats = harden(new Map());
const devices = harden(new Map());
// runQueue entries are {type, vatID, more..}. 'more' depends on type:
// * deliver: target, msg
// * notifyFulfillToData/notifyFulfillToTarget/notifyReject: kernelPromiseID
// * notifyFulfillToData/notifyFulfillToPresence/notifyReject: kernelPromiseID
const runQueue = [];

@@ -54,10 +38,2 @@

function getVat(vatID) {
const vat = vats.get(vatID);
if (vat === undefined) {
throw new Error(`unknown vatID '${vatID}'`);
}
return vat;
}
function allocateKernelPromiseIndex() {

@@ -69,135 +45,2 @@ const i = nextPromiseIndex;

function allocatePromiseIDForVat(vat) {
const i = vat.nextPromiseID;
vat.nextPromiseID += 1;
return i;
}
function allocateResolverIDForVat(vat) {
const i = vat.nextResolverID;
vat.nextResolverID += 1;
return i;
}
// we define three types of slot identifiers: inbound, neutral, outbound
// * outbound is what syscall.send(slots=) contains, it is always scoped to
// the sending vat, and the values are {type, id}. 'type' is either
// 'import' ("my import") or 'export'. Message targets are always imports.
// * middle is stored in runQueue, and contains {type: export, vatID, slotID}
// * inbound is passed into deliver(slots=), is always scoped to the
// receiving/target vat, and the values are {type, id} where 'type' is
// either 'export' (for arguments coming back home) or 'import' for
// arguments from other vats
//
// * To convert outbound->middle, we look up imports in kernelSlots, and
// just append the sending vatID to exports
//
// * To convert middle->inbound, we set attach type='export' when the vatID
// matches that of the receiving vat, and we look up the others in
// kernelSlots (adding one if necessary) to deliver type='import'
// mapOutbound: convert fromVatID-relative slot to absolute slot. fromVatID
// just did syscall.send and referenced 'slot' (in an argument, or as the
// target of a send), what are they talking about?
function mapOutbound(fromVatID, slot) {
// kdebug(`mapOutbound ${JSON.stringify(slot)}`);
const vat = getVat(fromVatID);
if (slot.type === 'export') {
// one of fromVatID's exports, so just make the vatID explicit
Nat(slot.id);
return { type: 'export', vatID: fromVatID, id: slot.id };
}
if (slot.type === 'import') {
// an import from somewhere else, so look in the sending Vat's table to
// translate into absolute form
Nat(slot.id);
return vat.imports.outbound.get(slot.id);
}
if (slot.type === 'promise') {
Nat(slot.id);
if (!vat.promises.outbound.has(slot.id)) {
throw new Error(`unknown promise slot '${slot.id}'`);
}
return { type: 'promise', id: vat.promises.outbound.get(slot.id) };
}
if (slot.type === 'resolver') {
Nat(slot.id);
if (!vat.resolvers.outbound.has(slot.id)) {
throw new Error(`unknown resolver slot '${slot.id}'`);
}
return { type: 'resolver', id: vat.resolvers.outbound.get(slot.id) };
}
throw Error(`unknown slot.type '${slot.type}'`);
}
function allocateImportIndex(vatID) {
const vat = getVat(vatID);
const i = vat.nextImportID;
vat.nextImportID = i + 1;
return i;
}
// mapInbound: convert from absolute slot to forVatID-relative slot. This
// is used when building the arguments for dispatch.deliver.
function mapInbound(forVatID, slot) {
// kdebug(`mapInbound for ${forVatID} of ${slot}`);
const vat = getVat(forVatID);
if (slot.type === 'export') {
const { vatID, id } = slot;
Nat(id);
if (vatID === forVatID) {
// this is returning home, so it's one of the target's exports
return { type: 'export', id };
}
const m = vat.imports;
const key = `${slot.type}.${vatID}.${id}`; // ugh javascript
if (!m.inbound.has(key)) {
// must add both directions
const newSlotID = Nat(allocateImportIndex(forVatID));
// kdebug(` adding ${newSlotID}`);
m.inbound.set(key, newSlotID);
m.outbound.set(newSlotID, harden({ type: 'export', vatID, id }));
}
return { type: 'import', id: m.inbound.get(key) };
}
if (slot.type === 'promise') {
const kernelPromiseID = slot.id;
Nat(kernelPromiseID);
const m = vat.promises;
if (!m.inbound.has(kernelPromiseID)) {
const promiseID = Nat(allocatePromiseIDForVat(vat));
m.inbound.set(kernelPromiseID, promiseID);
m.outbound.set(promiseID, kernelPromiseID);
}
return { type: 'promise', id: m.inbound.get(kernelPromiseID) };
}
if (slot.type === 'resolver') {
const kernelPromiseID = slot.id;
Nat(kernelPromiseID);
const m = vat.resolvers;
if (!m.inbound.has(kernelPromiseID)) {
const resolverID = Nat(allocateResolverIDForVat(vat));
kdebug(
` mapInbound allocating resID ${resolverID} for kpid ${kernelPromiseID}`,
);
m.inbound.set(kernelPromiseID, resolverID);
m.outbound.set(resolverID, kernelPromiseID);
}
return { type: 'resolver', id: m.inbound.get(kernelPromiseID) };
}
throw Error(`unknown type '${slot.type}'`);
}
/*

@@ -250,3 +93,3 @@ function chaseRedirections(promiseID) {

reject(msg.kernelPromiseID, makeError(s), []);
} else if (kp.state === 'fulfilledToTarget') {
} else if (kp.state === 'fulfilledToPresence') {
send(kp.fulfillSlot, msg);

@@ -290,3 +133,3 @@ } else if (kp.state === 'rejected') {

function fulfillToTarget(id, targetSlot) {
function fulfillToPresence(id, targetSlot) {
if (!kernelPromises.has(id)) {

@@ -301,9 +144,9 @@ throw new Error(`unknown kernelPromise id '${id}'`);

throw new Error(
`fulfillToTarget() must fulfill to export, not ${targetSlot.type}`,
`fulfillToPresence() must fulfill to export, not ${targetSlot.type}`,
);
}
p.state = 'fulfilledToTarget';
p.state = 'fulfilledToPresence';
p.fulfillSlot = targetSlot;
notifySubscribersAndQueue(id, p, 'notifyFulfillToTarget');
notifySubscribersAndQueue(id, p, 'notifyFulfillToPresence');
delete p.subscribers;

@@ -350,2 +193,10 @@ delete p.decider;

function invoke(device, method, data, slots) {
const dev = devices.get(device.deviceName);
if (!dev) {
throw new Error(`unknown deviceRef ${JSON.stringify(device)}`);
}
return dev.manager.invoke(device, method, data, slots);
}
async function process(f, then, logerr) {

@@ -372,4 +223,2 @@ // the delivery might cause some number of (native) Promises to be

kdebug,
mapOutbound,
mapInbound,
createPromiseWithDecider,

@@ -380,9 +229,10 @@ send,

fulfillToData,
fulfillToTarget,
fulfillToPresence,
reject,
log,
process,
invoke,
};
function addVat(vatID, setup, devices) {
function addVat(vatID, setup) {
if (vats.has(vatID)) {

@@ -394,2 +244,3 @@ throw new Error(`already have a vat named '${vatID}'`);

makeLiveSlots,
makeCommsSlots,
log(str) {

@@ -400,29 +251,34 @@ log.push(`${str}`);

// the vatManager invokes setup() to build the userspace image
const manager = makeVatManager(
const manager = makeVatManager(vatID, syscallManager, setup, helpers);
vats.set(
vatID,
harden({
id: vatID,
manager,
}),
);
}
function addDevice(name, setup, endowments) {
if (devices.has(name)) {
throw new Error(`already have a device named '${name}'`);
}
const helpers = harden({
name,
makeDeviceSlots,
log(str) {
log.push(`${str}`);
},
});
const manager = makeDeviceManager(
name,
syscallManager,
setup,
helpers,
devices,
endowments,
);
// the vat record is not hardened: it holds mutable next-ID values
vats.set(vatID, {
id: vatID,
devices.set(name, {
id: name,
manager,
imports: harden({
outbound: new Map(),
inbound: new Map(),
}),
// make these IDs start at different values to detect errors better
nextImportID: 10,
promises: harden({
outbound: new Map(),
inbound: new Map(),
}),
nextPromiseID: 20,
resolvers: harden({
outbound: new Map(),
inbound: new Map(),
}),
nextResolverID: 30,
});

@@ -432,3 +288,3 @@ }

function addImport(forVatID, what) {
return mapInbound(forVatID, what);
return vats.get(forVatID).manager.mapInbound(what);
}

@@ -444,2 +300,9 @@

}
if (s.type === 'device') {
return harden({
type: `${s.type}`,
deviceName: `${s.deviceName}`,
id: Nat(s.id),
});
}
throw Error(`unrecognized type '${s.type}'`);

@@ -498,22 +361,49 @@ }

// bootstrap object to call itself, though.
const vref = harden({}); // marker
const vref = harden({
toString() {
return targetVatID;
},
}); // marker
vatObj0s[targetVatID] = vref;
vrefs.set(vref, { type: 'export', vatID: targetVatID, id: 0 });
console.log(`adding vref ${targetVatID}`);
});
function serializeSlot(vref, slots, slotMap) {
if (!vrefs.has(vref)) {
console.log(`oops ${vref}`, vref);
throw Error('bootstrap got unexpected pass-by-presence');
}
if (!slotMap.has(vref)) {
const drefs = new Map();
// we cannot serialize empty objects as pass-by-copy, because we decided
// to make them pass-by-presence for use as EQ-able markers (eg for
// Purses). So if we don't have any devices defined, we must add a dummy
// entry to this object so it will serialize as pass-by-copy. We can
// remove the dummy entry after we add the 'addVat' device
const deviceObj0s = { _dummy: 'dummy' };
Array.from(devices.entries()).forEach(d => {
const name = d[0];
const dref = harden({});
deviceObj0s[name] = dref;
drefs.set(dref, { type: 'device', deviceName: name, id: 0 });
console.log(`adding dref ${name}`);
});
if (Object.getOwnPropertyNames(deviceObj0s) === 0) {
throw new Error('pass-by-copy rules require at least one device');
}
function serializeSlot(ref, slots, slotMap) {
if (!slotMap.has(ref)) {
const slotIndex = slots.length;
slots.push(vrefs.get(vref));
slotMap.set(vref, slotIndex);
if (vrefs.has(ref)) {
slots.push(vrefs.get(ref));
slotMap.set(ref, slotIndex);
} else if (drefs.has(ref)) {
slots.push(drefs.get(ref));
slotMap.set(ref, slotIndex);
} else {
console.log(`oops ${ref}`, ref);
throw Error('bootstrap got unexpected pass-by-presence');
}
}
const slotIndex = slotMap.get(vref);
const slotIndex = slotMap.get(ref);
return harden({ [QCLASS]: 'slot', index: slotIndex });
}
const m = makeMarshal(serializeSlot);
const s = m.serialize(harden({ args: [argv, vatObj0s] }));
const s = m.serialize(harden({ args: [argv, vatObj0s, deviceObj0s] }));
// queueToExport() takes 'neutral' { type: export, vatID, slotID } objects in s.slots

@@ -532,21 +422,3 @@ queueToExport(vatID, 0, 'bootstrap', s.argsString, s.slots);

vatTables.push(vatTable);
vat.imports.outbound.forEach((target, slot) => {
kernelTable.push([
vatID,
'import',
slot,
target.type,
target.vatID,
target.id,
]);
});
vat.promises.outbound.forEach((kernelPromiseID, id) => {
kernelTable.push([vatID, 'promise', id, kernelPromiseID]);
});
vat.resolvers.outbound.forEach((kernelPromiseID, id) => {
kernelTable.push([vatID, 'resolver', id, kernelPromiseID]);
});
vat.manager.dumpTables().forEach(e => kernelTable.push(e));
});

@@ -593,3 +465,3 @@

const kernel = harden({
addVat(vatID, setup, devices = {}) {
addVat(vatID, setup) {
harden(setup);

@@ -602,13 +474,14 @@ // 'setup' must be an in-realm function. This test guards against

}
const kernelRealmDevices = {};
Object.getOwnPropertyNames(devices).forEach(name => {
const d = devices[name];
if (!(d instanceof Object)) {
throw Error(`device[${name}] is not an in-realm object`);
}
kernelRealmDevices[name] = d;
});
addVat(`${vatID}`, setup, kernelRealmDevices);
addVat(`${vatID}`, setup);
},
addDevice(deviceName, setup, endowments) {
console.log(`kernel.addDevice(${deviceName})`);
harden(setup);
if (!(setup instanceof Function)) {
throw Error('setup is not an in-realm function');
}
addDevice(`${deviceName}`, setup, endowments);
},
callBootstrap,

@@ -664,23 +537,9 @@

vats.forEach((vat, vatID) => {
vatTables[vatID] = {
nextImportID: vat.nextImportID,
imports: {
outbound: Array.from(vat.imports.outbound.entries()),
inbound: Array.from(vat.imports.inbound.entries()),
},
vatTables[vatID] = { state: vat.manager.getCurrentState() };
Object.assign(vatTables[vatID], vat.manager.getManagerState());
});
nextPromiseID: vat.nextPromiseID,
promises: {
outbound: Array.from(vat.promises.outbound.entries()),
inbound: Array.from(vat.promises.inbound.entries()),
},
nextResolverID: vat.nextResolverID,
resolvers: {
outbound: Array.from(vat.resolvers.outbound.entries()),
inbound: Array.from(vat.resolvers.inbound.entries()),
},
state: vat.manager.getCurrentState(),
};
const deviceState = {};
devices.forEach((d, deviceName) => {
deviceState[deviceName] = d.manager.getCurrentState();
});

@@ -700,2 +559,3 @@

vats: vatTables,
devices: deviceState,
runQueue,

@@ -722,37 +582,2 @@ promises,

const vat = vats.get(vatID);
if (
vat.imports.outbound.size ||
vat.imports.inbound.size ||
vat.promises.inbound.size ||
vat.promises.inbound.size ||
vat.resolvers.inbound.size ||
vat.resolvers.inbound.size
) {
throw new Error(`vat[$vatID] is not empty, cannot loadState`);
}
vat.nextImportID = vatData.nextImportID;
vatData.imports.outbound.forEach(kv =>
vat.imports.outbound.set(kv[0], kv[1]),
);
vatData.imports.inbound.forEach(kv =>
vat.imports.inbound.set(kv[0], kv[1]),
);
vat.nextPromiseID = vatData.nextPromiseID;
vatData.promises.outbound.forEach(kv =>
vat.promises.outbound.set(kv[0], kv[1]),
);
vatData.promises.inbound.forEach(kv =>
vat.promises.inbound.set(kv[0], kv[1]),
);
vat.nextResolverID = vatData.nextResolverID;
vatData.resolvers.outbound.forEach(kv =>
vat.resolvers.outbound.set(kv[0], kv[1]),
);
vatData.resolvers.inbound.forEach(kv =>
vat.resolvers.inbound.set(kv[0], kv[1]),
);
// this shouldn't be doing any syscalls, which is good because we

@@ -762,4 +587,11 @@ // haven't wired anything else up yet

await vat.manager.loadState(vatData.state);
vat.manager.loadManagerState(vatData);
}
for (const deviceName of Object.getOwnPropertyNames(state.devices)) {
const deviceData = state.devices[deviceName];
const device = devices.get(deviceName);
device.manager.loadState(deviceData);
}
state.runQueue.forEach(q => runQueue.push(q));

@@ -766,0 +598,0 @@

@@ -11,5 +11,2 @@ import harden from '@agoric/harden';

// The E() wrapper does not yet return a Promise for the result of the method
// call.
function build(syscall, _state, makeRoot, forVatID) {

@@ -29,2 +26,8 @@ const enableLSDebug = false;

function makeDeviceNode(id) {
return harden({
[`_deviceID_${id}`]() {},
});
}
const outstandingProxies = new WeakSet();

@@ -36,3 +39,4 @@

slot.type === 'import' ||
slot.type === 'promise'
slot.type === 'promise' ||
slot.type === 'deviceImport'
) {

@@ -144,2 +148,4 @@ return `${slot.type}-${Nat(slot.id)}`;

val = importPromise(slot.id);
} else if (slot.type === 'deviceImport') {
val = makeDeviceNode(slot.id);
} else {

@@ -284,2 +290,4 @@ throw Error(`unrecognized slot.type '${slot.type}'`);

handler = PresenceHandler(slot);
} else if (slot && slot.type === 'deviceImport') {
throw new Error(`E() does not accept device nodes`);
} else {

@@ -298,2 +306,33 @@ // might be a local object (previously sent or not), or a local Promise

function DeviceHandler(slot) {
return {
get(target, prop) {
if (prop !== `${prop}`) {
return undefined;
}
return (...args) => {
const ser = m.serialize(harden({ args }));
const ret = syscall.callNow(slot, prop, ser.argsString, ser.slots);
const retval = m.unserialize(ret.data, ret.slots);
return retval;
};
},
};
}
function D(x) {
// results = D(devicenode).name(args)
if (outstandingProxies.has(x)) {
throw new Error('D(D(x)) is invalid');
}
const slot = valToSlot.get(x);
if (!slot || slot.type !== 'deviceImport') {
throw new Error('D() must be given a device node');
}
const handler = DeviceHandler(slot);
const pr = harden(new Proxy({}, handler));
outstandingProxies.add(pr);
return pr;
}
function deliver(facetid, method, argsbytes, caps, resolverID) {

@@ -347,3 +386,3 @@ lsdebug(

if (slot.type === 'import' || slot.type === 'export') {
syscall.fulfillToTarget(resolverID, slot);
syscall.fulfillToPresence(resolverID, slot);
}

@@ -386,4 +425,4 @@ } else {

function notifyFulfillToTarget(promiseID, slot) {
lsdebug(`ls.dispatch.notifyFulfillToTarget(${promiseID}, ${slot})`);
function notifyFulfillToPresence(promiseID, slot) {
lsdebug(`ls.dispatch.notifyFulfillToPresence(${promiseID}, ${slot})`);
if (!importedPromisesByPromiseID.has(promiseID)) {

@@ -405,3 +444,3 @@ throw new Error(`unknown promiseID '${promiseID}'`);

const rootObject = makeRoot(E);
const rootObject = makeRoot(E, D);
mustPassByPresence(rootObject);

@@ -417,3 +456,3 @@ const rootSlot = { type: 'export', id: 0 };

notifyFulfillToData,
notifyFulfillToTarget,
notifyFulfillToPresence,
notifyReject,

@@ -427,3 +466,3 @@ };

notifyFulfillToData,
notifyFulfillToTarget,
notifyFulfillToPresence,
notifyReject,

@@ -434,3 +473,3 @@ } = build(syscall, state, makeRoot, forVatID);

notifyFulfillToData,
notifyFulfillToTarget,
notifyFulfillToPresence,
notifyReject,

@@ -437,0 +476,0 @@ });

import harden from '@agoric/harden';
import Nat from '@agoric/nat';
export default function makeVatManager(
vatID,
syscallManager,
setup,
helpers,
devices,
) {
export default function makeVatManager(vatID, syscallManager, setup, helpers) {
const {
kdebug,
mapOutbound,
mapInbound,
createPromiseWithDecider,

@@ -20,8 +12,251 @@ send,

fulfillToData,
fulfillToTarget,
fulfillToPresence,
reject,
log,
process,
invoke,
} = syscallManager;
// We use vat-centric terminology here, so "inbound" means "into a vat",
// generally from the kernel. We also have "comms vats" which use special
// device access to interact with remote machines: messages from those
// remote machines are "inbound" into the comms vats. Conversely "outbound"
// means "out of a vat", usually into the local kernel, but we also use
// "outbound" to describe the messages a comms vat is sending over a socket
// or other communications channel.
// The mapInbound() function is used to translate slot references on the
// vat->kernel pathway. mapOutbound() is used for kernel->vat.
// The terms "import" and "export" are also vat-centric. "import" means
// something a Vat has imported (from the kernel). Imports are tracked in a
// kernel-side table for each Vat, which is populated by the kernel as a
// message is delivered. Each import is represented inside the Vat as a
// Presence (at least when using liveSlots).
// "exports" are callable objects inside the Vat which it has made
// available to the kernel (so that other vats can invoke it). The exports
// table is managed by userspace code inside the vat. The kernel tables map
// one vat's import IDs to a pair of (exporting vat, vat's export-id) in
// `vats[vatid].imports.outbound[importID]`. To make sure we use the same
// importID each time, we also need to keep a reverse table:
// `vats[vatid].imports.inbound` maps the (exporting-vat, export-id) back
// to the importID.
// Comms vats will have their own internal tables to track references
// shared with other machines. These will have mapInbound/mapOutbound too.
// A message arriving on a communication channel will pass through the
// comms vat's mapInbound to figure out which "machine export" is the
// target, which maps to a "vat import" (coming from the kernel). The
// arguments might also be machine exports (for arguments that are "coming
// home"), or more commonly will be new machine imports (for arguments that
// point to other machines, usually the sending machine). The machine
// imports will be presented to the kernel as exports of the comms vat.
// 'id' is always a non-negative integer (a Nat)
// 'slot' is a type(string)+id, plus maybe a vatid (string)
// * 'relative slot' lacks a vatid because it is implicit
// used in syscall.send and dispatch.deliver
// * 'absolute slot' includes an explicit vatid
// used in kernel's runQueue
// 'slots' is an array of 'slot', in send/deliver
// 'slotIndex' is an index into 'slots', used in serialized JSON
// key = `${type}.${toVatID}.${slotID}`
// tables = { imports: { inbound[key] = importID,
// outbound[importID] = slot, },
// nextImportID,
// promises: { inbound[kernelPromiseID] = id,
// outbound[id] = kernelPromiseID, },
// nextPromiseID,
// resolvers: { inbound[kernelPromiseID] = id,
// outbound[id] = kernelPromiseID, },
// nextResolverID,
// }
// per-vat translation tables
const tables = {
imports: harden({
outbound: new Map(),
inbound: new Map(),
}),
// make these IDs start at different values to detect errors better
nextImportID: 10,
promises: harden({
outbound: new Map(),
inbound: new Map(),
}),
nextPromiseID: 20,
resolvers: harden({
outbound: new Map(),
inbound: new Map(),
}),
nextResolverID: 30,
nextDeviceImportID: 40,
deviceImports: harden({
outbound: new Map(),
inbound: new Map(),
}),
};
function allocatePromiseID() {
const i = tables.nextPromiseID;
tables.nextPromiseID += 1;
return i;
}
function allocateResolverID() {
const i = tables.nextResolverID;
tables.nextResolverID += 1;
return i;
}
function allocateDeviceImportID() {
const i = tables.nextDeviceImportID;
tables.nextDeviceImportID += 1;
return i;
}
// we define three types of slot identifiers: inbound, neutral, outbound
// * outbound is what syscall.send(slots=) contains, it is always scoped to
// the sending vat, and the values are {type, id}. 'type' is either
// 'import' ("my import") or 'export'. Message targets are always imports.
// * middle is stored in runQueue, and contains {type: export, vatID, slotID}
// * inbound is passed into deliver(slots=), is always scoped to the
// receiving/target vat, and the values are {type, id} where 'type' is
// either 'export' (for arguments coming back home) or 'import' for
// arguments from other vats
//
// * To convert outbound->middle, we look up imports in kernelSlots, and
// just append the sending vatID to exports
//
// * To convert middle->inbound, we set attach type='export' when the vatID
// matches that of the receiving vat, and we look up the others in
// kernelSlots (adding one if necessary) to deliver type='import'
// mapOutbound: convert fromVatID-relative slot to absolute slot. fromVatID
// just did syscall.send and referenced 'slot' (in an argument, or as the
// target of a send), what are they talking about?
function mapOutbound(slot) {
// kdebug(`mapOutbound ${JSON.stringify(slot)}`);
if (slot.type === 'export') {
// one of our exports, so just make the vatID explicit
Nat(slot.id);
return { type: 'export', vatID, id: slot.id };
}
if (slot.type === 'import') {
// an import from somewhere else, so look in the sending Vat's table to
// translate into absolute form
Nat(slot.id);
return tables.imports.outbound.get(slot.id);
}
if (slot.type === 'deviceImport') {
Nat(slot.id);
return tables.deviceImports.outbound.get(slot.id);
}
if (slot.type === 'promise') {
Nat(slot.id);
if (!tables.promises.outbound.has(slot.id)) {
throw new Error(`unknown promise slot '${slot.id}'`);
}
return { type: 'promise', id: tables.promises.outbound.get(slot.id) };
}
if (slot.type === 'resolver') {
Nat(slot.id);
if (!tables.resolvers.outbound.has(slot.id)) {
throw new Error(`unknown resolver slot '${slot.id}'`);
}
return { type: 'resolver', id: tables.resolvers.outbound.get(slot.id) };
}
throw Error(`unknown slot.type in '${JSON.stringify(slot)}'`);
}
function allocateImportIndex() {
const i = tables.nextImportID;
tables.nextImportID = i + 1;
return i;
}
// mapInbound: convert from absolute slot to forVatID-relative slot. This
// is used when building the arguments for dispatch.deliver.
function mapInbound(slot) {
kdebug(`mapInbound for ${vatID} of ${JSON.stringify(slot)}`);
if (slot.type === 'export') {
const { vatID: fromVatID, id } = slot;
Nat(id);
if (vatID === fromVatID) {
// this is returning home, so it's one of the target's exports
return { type: 'export', id };
}
const m = tables.imports;
const key = `${slot.type}.${fromVatID}.${id}`; // ugh javascript
if (!m.inbound.has(key)) {
// must add both directions
const newSlotID = Nat(allocateImportIndex());
// kdebug(` adding ${newSlotID}`);
m.inbound.set(key, newSlotID);
m.outbound.set(
newSlotID,
harden({ type: 'export', vatID: fromVatID, id }), // TODO just 'slot'?
);
}
return { type: 'import', id: m.inbound.get(key) };
}
if (slot.type === 'device') {
const { deviceName, id } = slot;
Nat(id);
const m = tables.deviceImports;
const key = `${slot.type}.${deviceName}.${id}`; // ugh javascript
if (!m.inbound.has(key)) {
// must add both directions
const newSlotID = Nat(allocateDeviceImportID());
// kdebug(` adding ${newSlotID}`);
m.inbound.set(key, newSlotID);
m.outbound.set(newSlotID, harden(slot));
}
return { type: 'deviceImport', id: m.inbound.get(key) };
}
if (slot.type === 'promise') {
const kernelPromiseID = slot.id;
Nat(kernelPromiseID);
const m = tables.promises;
if (!m.inbound.has(kernelPromiseID)) {
const promiseID = Nat(allocatePromiseID());
m.inbound.set(kernelPromiseID, promiseID);
m.outbound.set(promiseID, kernelPromiseID);
}
return { type: 'promise', id: m.inbound.get(kernelPromiseID) };
}
if (slot.type === 'resolver') {
const kernelPromiseID = slot.id;
Nat(kernelPromiseID);
const m = tables.resolvers;
if (!m.inbound.has(kernelPromiseID)) {
const resolverID = Nat(allocateResolverID());
kdebug(
` mapInbound allocating resID ${resolverID} for kpid ${kernelPromiseID}`,
);
m.inbound.set(kernelPromiseID, resolverID);
m.outbound.set(resolverID, kernelPromiseID);
}
return { type: 'resolver', id: m.inbound.get(kernelPromiseID) };
}
throw Error(`unknown type '${slot.type}'`);
}
let inReplay = false;

@@ -53,3 +288,3 @@ const transcript = [];

}
const target = mapOutbound(vatID, targetSlot);
const target = mapOutbound(targetSlot);
if (!target) {

@@ -67,3 +302,3 @@ throw Error(

);
const slots = vatSlots.map(slot => mapOutbound(vatID, slot));
const slots = vatSlots.map(slot => mapOutbound(slot));
// who will decide the answer? If the message is being queued for a

@@ -86,3 +321,3 @@ // promise, then the kernel will decide (when the answer gets

send(target, msg);
const p = mapInbound(vatID, {
const p = mapInbound({
type: 'promise',

@@ -96,7 +331,7 @@ id: kernelPromiseID,

const kernelPromiseID = createPromiseWithDecider(vatID);
const p = mapInbound(vatID, {
const p = mapInbound({
type: 'promise',
id: kernelPromiseID,
});
const r = mapInbound(vatID, {
const r = mapInbound({
type: 'resolver',

@@ -114,3 +349,3 @@ id: kernelPromiseID,

function doSubscribe(promiseID) {
const { id } = mapOutbound(vatID, {
const { id } = mapOutbound({
type: 'promise',

@@ -138,5 +373,5 @@ id: promiseID,

// otherwise it's already resolved, you probably want to know how
} else if (p.state === 'fulfilledToTarget') {
} else if (p.state === 'fulfilledToPresence') {
runQueue.push({
type: 'notifyFulfillToTarget',
type: 'notifyFulfillToPresence',
vatID,

@@ -164,3 +399,3 @@ kernelPromiseID: id,

function doRedirect(resolverID, targetPromiseID) {
const { id } = mapOutbound(vatID, { type: 'resolver', id: resolverID });
const { id } = mapOutbound({ type: 'resolver', id: resolverID });
if (!kernelPromises.has(id)) {

@@ -174,3 +409,3 @@ throw new Error(`unknown kernelPromise id '${id}'`);

let { id: targetID } = mapOutbound(vatID, { type: 'promise', id: targetPromiseID });
let { id: targetID } = mapOutbound({ type: 'promise', id: targetPromiseID });
if (!kernelPromises.has(targetID)) {

@@ -199,3 +434,3 @@ throw new Error(`unknown kernelPromise id '${targetID}'`);

Nat(resolverID);
const { id } = mapOutbound(vatID, {
const { id } = mapOutbound({
type: 'resolver',

@@ -207,3 +442,3 @@ id: resolverID,

}
const slots = vatSlots.map(slot => mapOutbound(vatID, slot));
const slots = vatSlots.map(slot => mapOutbound(slot));
kdebug(

@@ -217,5 +452,5 @@ `syscall[${vatID}].fulfillData(vatid=${resolverID}/kid=${id}) = ${fulfillData} v=${JSON.stringify(

function doFulfillToTarget(resolverID, slot) {
function doFulfillToPresence(resolverID, slot) {
Nat(resolverID);
const { id } = mapOutbound(vatID, {
const { id } = mapOutbound({
type: 'resolver',

@@ -228,9 +463,9 @@ id: resolverID,

const targetSlot = mapOutbound(vatID, slot);
const targetSlot = mapOutbound(slot);
kdebug(
`syscall[${vatID}].fulfillToTarget(vatid=${resolverID}/kid=${id}) = vat:${JSON.stringify(
`syscall[${vatID}].fulfillToPresence(vatid=${resolverID}/kid=${id}) = vat:${JSON.stringify(
targetSlot,
)}=ker:${id})`,
);
fulfillToTarget(id, targetSlot);
fulfillToPresence(id, targetSlot);
}

@@ -240,3 +475,3 @@

Nat(resolverID);
const { id } = mapOutbound(vatID, {
const { id } = mapOutbound({
type: 'resolver',

@@ -248,3 +483,3 @@ id: resolverID,

}
const slots = vatSlots.map(slot => mapOutbound(vatID, slot));
const slots = vatSlots.map(slot => mapOutbound(slot));
kdebug(

@@ -258,2 +493,20 @@ `syscall[${vatID}].reject(vatid=${resolverID}/kid=${id}) = ${rejectData} v=${JSON.stringify(

function doCallNow(target, method, argsString, argsSlots) {
const dev = mapOutbound(target);
if (dev.type !== 'device') {
throw new Error(
`doCallNow must target a device, not ${JSON.stringify(target)}`,
);
}
const slots = argsSlots.map(slot => mapOutbound(slot));
kdebug(
`syscall[${vatID}].callNow(vat:device:${target.id}=ker:${JSON.stringify(
dev,
)}).${method}`,
);
const ret = invoke(dev, method, argsString, slots);
const retSlots = ret.slots.map(slot => mapInbound(slot));
return harden({ data: ret.data, slots: retSlots });
}
function replay(name, ...args) {

@@ -290,7 +543,7 @@ const s = playbackSyscalls.shift();

},
fulfillToTarget(...args) {
transcriptAddSyscall(['fulfillToTarget', ...args]);
fulfillToPresence(...args) {
transcriptAddSyscall(['fulfillToPresence', ...args]);
return inReplay
? replay('fulfillToTarget', ...args)
: doFulfillToTarget(...args);
? replay('fulfillToPresence', ...args)
: doFulfillToPresence(...args);
},

@@ -302,2 +555,8 @@ reject(...args) {

callNow(...args) {
const ret = inReplay ? replay('callNow', ...args) : doCallNow(...args);
transcriptAddSyscall(['callNow', ...args], ret);
return ret;
},
log(str) {

@@ -321,3 +580,6 @@ log.push(`${str}`);

const dispatch = setup(syscall, state, helpers, devices);
const dispatch = setup(syscall, state, helpers);
if (!dispatch || dispatch.deliver === undefined) {
throw new Error(`vat setup() failed to return a 'dispatch' with .deliver`);
}

@@ -352,6 +614,6 @@ // dispatch handlers: these are used by the kernel core

}
const inputSlots = msg.slots.map(slot => mapInbound(vatID, slot));
const inputSlots = msg.slots.map(slot => mapInbound(slot));
const resolverID =
msg.kernelResolverID &&
mapInbound(vatID, { type: 'resolver', id: msg.kernelResolverID }).id;
mapInbound({ type: 'resolver', id: msg.kernelResolverID }).id;
return doProcess(

@@ -373,3 +635,3 @@ [

const { kernelPromiseID, vatID } = message;
const relativeID = mapInbound(vatID, {
const relativeID = mapInbound({
type: 'resolver',

@@ -386,7 +648,7 @@ id: kernelPromiseID,

const p = kernelPromises.get(kernelPromiseID);
const relativeID = mapInbound(vatID, {
const relativeID = mapInbound({
type: 'promise',
id: kernelPromiseID,
}).id;
const slots = p.fulfillSlots.map(slot => mapInbound(vatID, slot));
const slots = p.fulfillSlots.map(slot => mapInbound(slot));
return doProcess(

@@ -398,13 +660,13 @@ ['notifyFulfillToData', relativeID, p.fulfillData, slots],

if (type === 'notifyFulfillToTarget') {
if (type === 'notifyFulfillToPresence') {
const { kernelPromiseID } = message;
const p = kernelPromises.get(kernelPromiseID);
const relativeID = mapInbound(vatID, {
const relativeID = mapInbound({
type: 'promise',
id: kernelPromiseID,
}).id;
const slot = mapInbound(vatID, p.fulfillSlot);
const slot = mapInbound(p.fulfillSlot);
return doProcess(
['notifyFulfillToTarget', relativeID, slot],
`vat[${vatID}].promise[${relativeID}] fulfillToTarget failed`,
['notifyFulfillToPresence', relativeID, slot],
`vat[${vatID}].promise[${relativeID}] fulfillToPresence failed`,
);

@@ -416,7 +678,7 @@ }

const p = kernelPromises.get(kernelPromiseID);
const relativeID = mapInbound(vatID, {
const relativeID = mapInbound({
type: 'promise',
id: kernelPromiseID,
}).id;
const slots = p.rejectSlots.map(slot => mapInbound(vatID, slot));
const slots = p.rejectSlots.map(slot => mapInbound(slot));
return doProcess(

@@ -431,2 +693,38 @@ ['notifyReject', relativeID, p.rejectData, slots],

function loadManagerState(vatData) {
if (
tables.imports.outbound.size ||
tables.imports.inbound.size ||
tables.promises.inbound.size ||
tables.promises.inbound.size ||
tables.resolvers.inbound.size ||
tables.resolvers.inbound.size
) {
throw new Error(`vat[$vatID] is not empty, cannot loadState`);
}
tables.nextImportID = vatData.nextImportID;
vatData.imports.outbound.forEach(kv =>
tables.imports.outbound.set(kv[0], kv[1]),
);
vatData.imports.inbound.forEach(kv =>
tables.imports.inbound.set(kv[0], kv[1]),
);
tables.nextPromiseID = vatData.nextPromiseID;
vatData.promises.outbound.forEach(kv =>
tables.promises.outbound.set(kv[0], kv[1]),
);
vatData.promises.inbound.forEach(kv =>
tables.promises.inbound.set(kv[0], kv[1]),
);
tables.nextResolverID = vatData.nextResolverID;
vatData.resolvers.outbound.forEach(kv =>
tables.resolvers.outbound.set(kv[0], kv[1]),
);
vatData.resolvers.inbound.forEach(kv =>
tables.resolvers.inbound.set(kv[0], kv[1]),
);
}
async function loadState(savedState) {

@@ -436,2 +734,3 @@ if (!useTranscript) {

}
inReplay = true;

@@ -447,2 +746,24 @@ for (let i = 0; i < savedState.transcript.length; i += 1) {

function getManagerState() {
return {
nextImportID: tables.nextImportID,
imports: {
outbound: Array.from(tables.imports.outbound.entries()),
inbound: Array.from(tables.imports.inbound.entries()),
},
nextPromiseID: tables.nextPromiseID,
promises: {
outbound: Array.from(tables.promises.outbound.entries()),
inbound: Array.from(tables.promises.inbound.entries()),
},
nextResolverID: tables.nextResolverID,
resolvers: {
outbound: Array.from(tables.resolvers.outbound.entries()),
inbound: Array.from(tables.resolvers.inbound.entries()),
},
};
}
function getCurrentState() {

@@ -452,4 +773,30 @@ return { transcript: Array.from(transcript) };

const manager = { loadState, processOneMessage, getCurrentState };
function dumpTables() {
const res = [];
tables.imports.outbound.forEach((target, slot) => {
res.push([vatID, 'import', slot, target.type, target.vatID, target.id]);
});
tables.promises.outbound.forEach((kernelPromiseID, id) => {
res.push([vatID, 'promise', id, kernelPromiseID]);
});
tables.resolvers.outbound.forEach((kernelPromiseID, id) => {
res.push([vatID, 'resolver', id, kernelPromiseID]);
});
kdebug(`vatMAnager.dumpTables ${JSON.stringify(res)}`);
return harden(res);
}
const manager = {
mapInbound,
mapOutbound,
dumpTables,
loadManagerState,
loadState,
processOneMessage,
getManagerState,
getCurrentState,
};
return manager;
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc