Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/ocapn/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';

import { makeClient } from './src/client/index.js';
import { makeTcpNetLayer } from './src/netlayers/tcp-test-only.js';
import { makeTagged, passStyleOf } from './src/pass-style-helpers.js';
import { encodeSwissnum } from './src/client/util.js';

export {
makeClient,
makeTcpNetLayer,
makeTagged,
passStyleOf,
encodeSwissnum,
E,
Far,
};
10 changes: 10 additions & 0 deletions packages/ocapn/src/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* @typedef {import('./types.js').SessionManager} SessionManager
* @typedef {import('./ocapn.js').GrantTracker} GrantTracker
* @typedef {import('./ocapn.js').Ocapn} Ocapn
* @typedef {import('./types.js').MarshalPlugin} MarshalPlugin
*/
import { makePromiseKit } from '@endo/promise-kit';
import { makeSyrupWriter } from '../syrup/encode.js';
Expand Down Expand Up @@ -155,6 +156,7 @@ const compareSessionKeysForCrossedHellos = (
* @param {GrantTracker} grantTracker
* @param {Map<string, any>} swissnumTable
* @param {Map<string, any>} giftTable
* @param {MarshalPlugin[]} marshalPlugins
* @param {any} message
*/
const handleSessionHandshakeMessage = (
Expand All @@ -165,6 +167,7 @@ const handleSessionHandshakeMessage = (
grantTracker,
swissnumTable,
giftTable,
marshalPlugins,
message,
) => {
logger.info(`handling handshake message of type ${message.type}`);
Expand Down Expand Up @@ -269,6 +272,7 @@ const handleSessionHandshakeMessage = (
swissnumTable,
giftTable,
'ocapn',
marshalPlugins,
);
const session = makeSession({
id: sessionId,
Expand Down Expand Up @@ -306,6 +310,7 @@ const handleSessionHandshakeMessage = (
* @param {GrantTracker} grantTracker
* @param {Map<string, any>} swissnumTable
* @param {Map<string, any>} giftTable
* @param {MarshalPlugin[]} marshalPlugins
* @param {Uint8Array} data
*/
const handleHandshakeMessageData = (
Expand All @@ -316,6 +321,7 @@ const handleHandshakeMessageData = (
grantTracker,
swissnumTable,
giftTable,
marshalPlugins,
data,
) => {
try {
Expand Down Expand Up @@ -346,6 +352,7 @@ const handleHandshakeMessageData = (
grantTracker,
swissnumTable,
giftTable,
marshalPlugins,
message,
);
} else {
Expand Down Expand Up @@ -457,13 +464,15 @@ const makeSessionManager = () => {
* @param {boolean} [options.verbose]
* @param {Map<string, any>} [options.swissnumTable]
* @param {Map<string, any>} [options.giftTable]
* @param {MarshalPlugin[]} [options.marshalPlugins]
* @returns {Client}
*/
export const makeClient = ({
debugLabel = 'ocapn',
verbose = false,
swissnumTable = new Map(),
giftTable = new Map(),
marshalPlugins = [],
} = {}) => {
/** @type {Map<string, NetLayer>} */
const netlayers = new Map();
Expand Down Expand Up @@ -535,6 +544,7 @@ export const makeClient = ({
grantTracker,
swissnumTable,
giftTable,
marshalPlugins,
data,
);
}
Expand Down
13 changes: 10 additions & 3 deletions packages/ocapn/src/client/ocapn.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* @typedef {import('./types.js').LocationId} LocationId
* @typedef {import('./types.js').OcapnPublicKey} OcapnPublicKey
* @typedef {import('./types.js').Session} Session
* @typedef {import('./types.js').MarshalPlugin} MarshalPlugin
* @typedef {import('../captp/captp-engine.js').CapTPEngine} CapTPEngine
* @typedef {import('../syrup/decode.js').SyrupReader} SyrupReader
* @typedef {import('../codecs/components.js').OcapnLocation} OcapnLocation
Expand Down Expand Up @@ -424,11 +425,12 @@ const makeMakeRemoteKit = ({
* @property {(message: any) => Uint8Array} writeOcapnMessage
*
* @param {TableKit} tableKit
* @param {MarshalPlugin[]} marshalPlugins
* @returns {CodecKit}
*/
const makeCodecKit = tableKit => {
const makeCodecKit = (tableKit, marshalPlugins) => {
const descCodecs = makeDescCodecs(tableKit);
const passableCodecs = makePassableCodecs(descCodecs);
const passableCodecs = makePassableCodecs(descCodecs, marshalPlugins);
const { readOcapnMessage, writeOcapnMessage } = makeOcapnOperationsCodecs(
descCodecs,
passableCodecs,
Expand Down Expand Up @@ -815,6 +817,7 @@ const makeBootstrapObject = (
* @param {Map<string, any>} swissnumTable
* @param {Map<string, any>} giftTable
* @param {string} [ourIdLabel]
* @param {MarshalPlugin[]} [marshalPlugins]
* @returns {Ocapn}
*/
export const makeOcapn = (
Expand All @@ -829,6 +832,7 @@ export const makeOcapn = (
swissnumTable,
giftTable,
ourIdLabel = 'OCapN',
marshalPlugins = [],
) => {
const commitSendSlots = () => {
logger.info(`commitSendSlots`);
Expand Down Expand Up @@ -1145,7 +1149,10 @@ export const makeOcapn = (
sendDepositGift,
);

const { readOcapnMessage, writeOcapnMessage } = makeCodecKit(tableKit);
const { readOcapnMessage, writeOcapnMessage } = makeCodecKit(
tableKit,
marshalPlugins,
);

function serializeAndSendMessage(message) {
// If we dont catch the error here it gets swallowed.
Expand Down
6 changes: 6 additions & 0 deletions packages/ocapn/src/client/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
* @property {(sessionId: Uint8Array) => OcapnPublicKey | undefined} getPeerPublicKeyForSessionId
*/

/**
* @typedef {object} MarshalPlugin
* @property {(value: any) => any} encode
* @property {(value: any) => any} decode
*/

/**
* @typedef {object} Client
* @property {Logger} logger
Expand Down
45 changes: 43 additions & 2 deletions packages/ocapn/src/codecs/passable.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/** @typedef {import('../syrup/codec.js').SyrupCodec} SyrupCodec */
/** @typedef {import('../syrup/codec.js').SyrupRecordCodec} SyrupRecordCodec */
/** @typedef {import('./descriptors.js').DescCodecs} DescCodecs */
/** @typedef {import('../client/types.js').MarshalPlugin} MarshalPlugin */

import { passStyleOf as realPassStyleOf } from '@endo/pass-style';
import {
Expand All @@ -26,6 +27,7 @@ import {
makeOcapnRecordCodecFromDefinition,
} from './util.js';

const { freeze } = Object;
const quote = JSON.stringify;

// OCapN Passable Atoms
Expand Down Expand Up @@ -78,16 +80,42 @@ const AtomCodecs = {
byteArray: BytestringCodec,
};

/**
* @param {any} value
* @param {MarshalPlugin[]} marshalPlugins
* @returns {any}
*/
const handleMarshalPluginsEncode = (value, marshalPlugins) => {
for (const plugin of marshalPlugins) {
const result = plugin.encode(value);
if (result !== undefined) {
return result;
}
}
return value;
};

const handleMarshalPluginsDecode = (value, marshalPlugins) => {
for (const plugin of marshalPlugins) {
const result = plugin.decode(value);
if (result !== undefined) {
return result;
}
}
return value;
};

/**
* @typedef {object} PassableCodecs
* @property {SyrupCodec} PassableCodec
*/

/**
* @param {DescCodecs} descCodecs
* @param {MarshalPlugin[]} marshalPlugins
* @returns {PassableCodecs}
*/
export const makePassableCodecs = descCodecs => {
export const makePassableCodecs = (descCodecs, marshalPlugins) => {
const { ReferenceCodec } = descCodecs;

// OCapN Passable Containers
Expand Down Expand Up @@ -211,7 +239,7 @@ export const makePassableCodecs = descCodecs => {
},
);

const OcapnPassableUnionCodec = makeTypeHintUnionCodec(
const OcapnPassableUnionInternalCodec = makeTypeHintUnionCodec(
'OcapnPassable',
// syrup type hint -> codec
{
Expand Down Expand Up @@ -286,6 +314,19 @@ export const makePassableCodecs = descCodecs => {
},
);

// OcapnPassable is a wrapper around OcapnPassableInternal that handles marshal plugins.
/** @type {SyrupCodec} */
const OcapnPassableUnionCodec = freeze({
read(syrupReader) {
const value = OcapnPassableUnionInternalCodec.read(syrupReader);
return handleMarshalPluginsDecode(value, marshalPlugins);
},
write(providedValue, syrupWriter) {
const value = handleMarshalPluginsEncode(providedValue, marshalPlugins);
return OcapnPassableUnionInternalCodec.write(value, syrupWriter);
},
});

const ContainerCodecs = {
list: makeListCodecFromEntryCodec('OcapnList', OcapnPassableUnionCodec),
struct: OcapnStructCodec,
Expand Down
67 changes: 65 additions & 2 deletions packages/ocapn/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

/** @typedef {import('../src/codecs/components.js').OcapnLocation} OcapnLocation */
/** @typedef {import('../src/client/types.js').Client} Client */
/** @typedef {import('../src/client/types.js').MarshalPlugin} MarshalPlugin */

import test from '@endo/ses-ava/prepare-endo.js';
import { E } from '@endo/eventual-send';
Expand All @@ -11,31 +12,45 @@ import { makeTcpNetLayer } from '../src/netlayers/tcp-test-only.js';
import { makeClient } from '../src/client/index.js';
import { testWithErrorUnwrapping } from './_util.js';
import { encodeSwissnum } from '../src/client/util.js';
import { makeTagged, passStyleOf } from '../src/pass-style-helpers.js';

/**
* @param {string} debugLabel
* @param {() => Map<string, any>} [makeDefaultSwissnumTable]
* @param {MarshalPlugin[]} [marshalPlugins]
* @returns {Promise<{ client: Client, location: OcapnLocation }>}
*/
const makeTestClient = async (debugLabel, makeDefaultSwissnumTable) => {
const makeTestClient = async (
debugLabel,
makeDefaultSwissnumTable,
marshalPlugins,
) => {
const client = makeClient({
debugLabel,
swissnumTable: makeDefaultSwissnumTable && makeDefaultSwissnumTable(),
marshalPlugins,
});
const tcpNetlayer = await makeTcpNetLayer({ client });
client.registerNetlayer(tcpNetlayer);
const { location } = tcpNetlayer;
return { client, location };
};

const makeTestClientPair = async makeDefaultSwissnumTable => {
/**
* @param {() => Map<string, any>} makeDefaultSwissnumTable
* @param {MarshalPlugin[]} [marshalPlugins]
* @returns {Promise<{ clientA: Client, clientB: Client, locationB: OcapnLocation, shutdownBoth: () => void, ocapnA: Ocapn, bootstrapB: any }>}
*/
const makeTestClientPair = async (makeDefaultSwissnumTable, marshalPlugins) => {
const { client: clientA } = await makeTestClient(
'A',
makeDefaultSwissnumTable,
marshalPlugins,
);
const { client: clientB, location: locationB } = await makeTestClient(
'B',
makeDefaultSwissnumTable,
marshalPlugins,
);
const shutdownBoth = () => {
clientA.shutdown();
Expand Down Expand Up @@ -134,3 +149,51 @@ testWithErrorUnwrapping(
shutdownBoth();
},
);

test('marshal plugins roundtrip', async t => {
class Greeble {
constructor(id) {
this.id = id;
}
}

const testObjectTable = new Map();
testObjectTable.set(
'GreebleMaker',
Far('makeGreeble', id => {
return new Greeble(id);
}),
);

const GreeblePlugin = {
encode: value => {
if (value instanceof Greeble) {
return makeTagged('Greeble', value.id);
}
return undefined;
},
decode: value => {
if (
passStyleOf(value) === 'tagged' &&
value[Symbol.toStringTag] === 'Greeble'
) {
return new Greeble(value.payload);
}
return undefined;
},
};

const { bootstrapB: bootstrap, shutdownBoth } = await makeTestClientPair(
() => testObjectTable,
[GreeblePlugin],
);

const makeGreeble = E(bootstrap).fetch(encodeSwissnum('GreebleMaker'));
const greeble = await E(makeGreeble)(123n);

// Sanity check
t.is(greeble.id, 123n, 'Greeble id is 123');
t.truthy(greeble instanceof Greeble, 'Greeble is a Greeble');

shutdownBoth();
});
2 changes: 1 addition & 1 deletion packages/ocapn/test/codecs/_codecs_util.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export const makeCodecTestKit = (peerLocation = defaultPeerLocation) => {
sendDepositGift,
);
const descCodecs = makeDescCodecs(tableKit);
const passableCodecs = makePassableCodecs(descCodecs);
const passableCodecs = makePassableCodecs(descCodecs, []);
const { OcapnMessageUnionCodec } = makeOcapnOperationsCodecs(
descCodecs,
passableCodecs,
Expand Down
4 changes: 4 additions & 0 deletions packages/pbcap/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
Loading
Loading