Skip to content

Introduce initial "ocapn" package #2766

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 17, 2025
Merged
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
4 changes: 4 additions & 0 deletions packages/ocapn/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.
File renamed without changes.
File renamed without changes.
8 changes: 8 additions & 0 deletions packages/ocapn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

# OCapN

🚧 Work in progress.

An implementation of the (OCapN protocol)[https://github.com/ocapn/ocapn/].

Includes codecs for (Syrup encoded messages)[https://github.com/ocapn/syrup/].
File renamed without changes.
Empty file added packages/ocapn/index.js
Empty file.
43 changes: 21 additions & 22 deletions packages/syrup/package.json → packages/ocapn/package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
{
"name": "@endo/syrup",
"version": "1.0.9",
"description": "Syrup is a language-independent binary serialization format for structured object trees",
"keywords": [
"syrup",
"codec",
"structured"
],
"name": "@endo/ocapn",
"version": "0.1.0",
"private": true,
"description": null,
"keywords": [],
"author": "Endo contributors",
"license": "Apache-2.0",
"homepage": "https://github.com/endojs/endo/blob/master/packages/syrup/README.md",
"homepage": "https://github.com/endojs/endo/tree/master/packages/ocapn#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/endojs/endo.git",
"directory": "packages/syrup"
"directory": "packages/ocapn"
},
"bugs": {
"url": "https://github.com/endojs/endo/issues"
Expand All @@ -23,22 +20,24 @@
"module": "./index.js",
"exports": {
".": "./index.js",
"./encode.js": "./encode.js",
"./decode.js": "./decode.js",
"./package.json": "./package.json"
},
"scripts": {
"build": "exit 0",
"prepack": "tsc --build tsconfig.build.json",
"postpack": "git clean -f '*.d.ts*'",
"cover": "c8 ava",
"lint": "yarn lint:types && yarn lint:eslint",
"lint-fix": "eslint --fix .",
"lint:eslint": "eslint .",
"lint-check": "yarn lint",
"lint-fix": "yarn lint:eslint --fix && yarn lint:types",
"lint:eslint": "eslint '**/*.js'",
"lint:types": "tsc",
"test": "ava"
"postpack": "git clean -f '*.d.ts*'",
"prepack": "tsc --build tsconfig.build.json",
"test": "ava",
"test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js",
"test:xs": "exit 0"
},
"devDependencies": {
"@endo/lockdown": "workspace:^",
"@endo/ses-ava": "workspace:^",
"ava": "^6.1.3",
"babel-eslint": "^10.1.0",
"c8": "^7.14.0",
Expand All @@ -48,6 +47,7 @@
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.29.1",
"prettier": "^3.3.3",
"tsd": "^0.31.2",
"typescript": "~5.6.3"
},
"files": [
Expand All @@ -61,7 +61,9 @@
"src",
"tools"
],
"private": true,
"publishConfig": {
"access": "public"
},
"eslintConfig": {
"extends": [
"plugin:@endo/internal"
Expand All @@ -72,8 +74,5 @@
"test/**/*.test.*"
],
"timeout": "2m"
},
"typeCoverage": {
"atLeast": 96.09
}
}
86 changes: 86 additions & 0 deletions packages/ocapn/src/codecs/components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// @ts-check

import { makeRecordUnionCodec } from '../syrup/codec.js';
import { makeOCapNRecordCodecFromDefinition } from './util.js';

/** @typedef {import('../syrup/codec.js').SyrupCodec} SyrupCodec */

const { freeze } = Object;

/*
* OCapN Components are used in both OCapN Messages and Descriptors
*/

/**
* @param {string} expectedLabel
* @returns {SyrupCodec}
*/
export const makeOCapNSignatureValueComponentCodec = expectedLabel => {
/**
* @param {import('../syrup/decode.js').SyrupReader} syrupReader
* @returns {Uint8Array}
*/
const read = syrupReader => {
const label = syrupReader.readSelectorAsString();
if (label !== expectedLabel) {
throw Error(`Expected label ${expectedLabel}, got ${label}`);
}
const value = syrupReader.readBytestring();
return value;
};
/**
* @param {Uint8Array} value
* @param {import('../syrup/encode.js').SyrupWriter} syrupWriter
*/
const write = (value, syrupWriter) => {
syrupWriter.writeSelectorFromString(expectedLabel);
syrupWriter.writeBytestring(value);
};
return freeze({ read, write });
};

const OCapNSignatureRValue = makeOCapNSignatureValueComponentCodec('r');
const OCapNSignatureSValue = makeOCapNSignatureValueComponentCodec('s');

export const OCapNSignature = makeOCapNRecordCodecFromDefinition('sig-val', [
['scheme', 'selector'],
['r', OCapNSignatureRValue],
['s', OCapNSignatureSValue],
]);

export const OCapNNode = makeOCapNRecordCodecFromDefinition('ocapn-node', [
['transport', 'selector'],
['address', 'bytestring'],
['hints', 'boolean'],
]);

export const OCapNSturdyRef = makeOCapNRecordCodecFromDefinition(
'ocapn-sturdyref',
[
['node', OCapNNode],
['swissNum', 'string'],
],
);

export const OCapNPublicKey = makeOCapNRecordCodecFromDefinition('public-key', [
['scheme', 'selector'],
['curve', 'selector'],
['flags', 'selector'],
['q', 'bytestring'],
]);

export const OCapNComponentUnionCodec = makeRecordUnionCodec({
OCapNNode,
OCapNSturdyRef,
OCapNPublicKey,
OCapNSignature,
});

export const readOCapComponent = syrupReader => {
return OCapNComponentUnionCodec.read(syrupReader);
};

export const writeOCapComponent = (component, syrupWriter) => {
OCapNComponentUnionCodec.write(component, syrupWriter);
return syrupWriter.getBytes();
};
89 changes: 89 additions & 0 deletions packages/ocapn/src/codecs/descriptors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { makeRecordUnionCodec } from '../syrup/codec.js';
import { makeOCapNRecordCodecFromDefinition } from './util.js';
import { PositiveIntegerCodec } from './subtypes.js';
import { OCapNNode, OCapNPublicKey, OCapNSignature } from './components.js';

/*
* These are OCapN Descriptors, they are Passables that are used both
* directly in OCapN Messages and as part of Passable structures.
*/

export const DescImportObject = makeOCapNRecordCodecFromDefinition(
'desc:import-object',
[['position', PositiveIntegerCodec]],
);

export const DescImportPromise = makeOCapNRecordCodecFromDefinition(
'desc:import-promise',
[['position', PositiveIntegerCodec]],
);

export const DescExport = makeOCapNRecordCodecFromDefinition('desc:export', [
['position', PositiveIntegerCodec],
]);

export const DescAnswer = makeOCapNRecordCodecFromDefinition('desc:answer', [
['position', PositiveIntegerCodec],
]);

export const DescHandoffGive = makeOCapNRecordCodecFromDefinition(
'desc:handoff-give',
[
['receiverKey', OCapNPublicKey],
['exporterLocation', OCapNNode],
['session', 'bytestring'],
['gifterSide', OCapNPublicKey],
['giftId', 'bytestring'],
],
);

export const DescSigGiveEnvelope = makeOCapNRecordCodecFromDefinition(
'desc:sig-envelope',
[
['object', DescHandoffGive],
['signature', OCapNSignature],
],
);

export const DescHandoffReceive = makeOCapNRecordCodecFromDefinition(
'desc:handoff-receive',
[
['receivingSession', 'bytestring'],
['receivingSide', 'bytestring'],
['handoffCount', PositiveIntegerCodec],
['signedGive', DescSigGiveEnvelope],
],
);

export const DescSigReceiveEnvelope = makeOCapNRecordCodecFromDefinition(
'desc:sig-envelope',
[
['object', DescHandoffReceive],
['signature', OCapNSignature],
],
);

// Note: this may only be useful for testing
export const OCapNDescriptorUnionCodec = makeRecordUnionCodec({
OCapNNode,
OCapNPublicKey,
OCapNSignature,
DescSigGiveEnvelope,
// TODO: ambiguous record label for DescSigGiveEnvelope and DescSigReceiveEnvelope
// DescSigReceiveEnvelope,
DescImportObject,
DescImportPromise,
DescExport,
DescAnswer,
DescHandoffGive,
DescHandoffReceive,
});

export const readOCapDescriptor = syrupReader => {
return OCapNDescriptorUnionCodec.read(syrupReader);
};

export const writeOCapDescriptor = (descriptor, syrupWriter) => {
OCapNDescriptorUnionCodec.write(descriptor, syrupWriter);
return syrupWriter.bufferWriter.subarray(0, syrupWriter.bufferWriter.length);
};
Loading
Loading