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
843 changes: 843 additions & 0 deletions packages/ocapn/docs/cbor-encoding.md

Large diffs are not rendered by default.

184 changes: 184 additions & 0 deletions packages/ocapn/docs/codec-usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# OCapN Codec Usage

This document describes the expected usage patterns for OCapN codecs.

## Design Principle: Dependency Injection

Applications should import only the codec implementations they actually use.
This ensures that unused implementations are not retained in the bundle,
enabling tree-shaking and minimizing application size.

**Do not** use a central factory that knows about all codecs. Instead, import
the specific codec components you need and inject them where required.

## Available Codecs

### Syrup (Codec 0)

Syrup is a canonical, text-delimited binary serialization format. It is the
original OCapN wire format.

```js
// Import only the writer
import { makeSyrupWriter } from '@endo/ocapn/syrup/encode.js';

// Import only the reader
import { makeSyrupReader } from '@endo/ocapn/syrup/decode.js';

// Or import both from the index
import { makeSyrupWriter, makeSyrupReader } from '@endo/ocapn/syrup/index.js';
```

#### Writing with Syrup

```js
import { makeSyrupWriter } from '@endo/ocapn/syrup/encode.js';

const writer = makeSyrupWriter({ name: 'my-message' });
writer.enterRecord();
writer.writeSelectorFromString('op:deliver');
writer.writeInteger(42n);
writer.writeString('hello');
writer.exitRecord();

const bytes = writer.getBytes();
```

#### Reading with Syrup

```js
import { makeSyrupReader } from '@endo/ocapn/syrup/decode.js';

const reader = makeSyrupReader(bytes, { name: 'my-message' });
reader.enterRecord();
const label = reader.readSelectorAsString(); // 'op:deliver'
const num = reader.readInteger(); // 42n
const str = reader.readString(); // 'hello'
reader.exitRecord();
```

### CBOR (Codec 1)

CBOR (Concise Binary Object Representation) is an RFC 8949 compliant binary
format. OCapN CBOR uses canonical encoding for deterministic serialization.

```js
// Import only the writer
import { makeCborWriter } from '@endo/ocapn/cbor/encode.js';

// Import only the reader
import { makeCborReader } from '@endo/ocapn/cbor/decode.js';

// Or import both from the index
import { makeCborWriter, makeCborReader } from '@endo/ocapn/cbor/index.js';
```

#### Writing with CBOR

```js
import { makeCborWriter } from '@endo/ocapn/cbor/encode.js';

const writer = makeCborWriter({ name: 'my-message' });
writer.enterRecord();
writer.writeSelectorFromString('op:deliver');
writer.writeInteger(42n);
writer.writeString('hello');
writer.exitRecord();

const bytes = writer.getBytes();
```

#### Reading with CBOR

```js
import { makeCborReader } from '@endo/ocapn/cbor/decode.js';

const reader = makeCborReader(bytes, { name: 'my-message' });
reader.enterRecord();
const label = reader.readSelectorAsString(); // 'op:deliver'
const num = reader.readInteger(); // 42n
const str = reader.readString(); // 'hello'
reader.exitRecord();
```

### CBOR Diagnostic Notation

CBOR diagnostic notation is a text-based representation useful for testing
and debugging. It is a codec in its own right:

```js
// Encode: CBOR bytes → diagnostic string
import { encode } from '@endo/ocapn/cbor/diagnostic/encode.js';

// Decode: diagnostic string → JavaScript values
import { decode } from '@endo/ocapn/cbor/diagnostic/decode.js';

// Or import from the index
import { encode, decode } from '@endo/ocapn/cbor/diagnostic/index.js';
```

## Injection Pattern for OCapN Client

When constructing an OCapN client or netlayer, inject the codec factories:

```js
import { makeCborWriter } from '@endo/ocapn/cbor/encode.js';
import { makeCborReader } from '@endo/ocapn/cbor/decode.js';

const netlayer = makeNetlayer({
makeWriter: makeCborWriter,
makeReader: makeCborReader,
// ... other options
});
```

Or for Syrup:

```js
import { makeSyrupWriter } from '@endo/ocapn/syrup/encode.js';
import { makeSyrupReader } from '@endo/ocapn/syrup/decode.js';

const netlayer = makeNetlayer({
makeWriter: makeSyrupWriter,
makeReader: makeSyrupReader,
// ... other options
});
```

## Interface Compatibility

Both Syrup and CBOR codecs implement the same `OcapnReader` and `OcapnWriter`
interfaces (defined in `src/codec-interface.js`), ensuring they can be used
interchangeably wherever these interfaces are expected.

```js
/**
* @param {import('./codec-interface.js').MakeWriter} makeWriter
* @param {import('./codec-interface.js').MakeReader} makeReader
*/
function createMessageHandler(makeWriter, makeReader) {
return {
encode(value) {
const writer = makeWriter({ name: 'encode' });
// ... write value
return writer.getBytes();
},
decode(bytes) {
const reader = makeReader(bytes, { name: 'decode' });
// ... read value
return result;
},
};
}
```

## Codec Identification

For protocol negotiation purposes:
- **Syrup**: Codec ID `0`
- **CBOR**: Codec ID `1`

These identifiers may be used during handshake to negotiate which codec
to use for a session. See the netlayer documentation for details on
codec negotiation.

1 change: 1 addition & 0 deletions packages/ocapn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@endo/ses-ava": "workspace:^",
"ava": "catalog:dev",
"c8": "catalog:dev",
"cbor": "^9.0.2",
"eslint": "catalog:dev",
"tsd": "catalog:dev",
"typescript": "~5.9.2"
Expand Down
118 changes: 118 additions & 0 deletions packages/ocapn/src/cbor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# OCapN CBOR Codec (Codec 1)

This directory contains a CBOR (Concise Binary Object Representation) encoder
and decoder for OCapN messages, as an alternative to the Syrup codec.

**Codec ID**: `1` (for protocol negotiation)

## Directory Structure

```
cbor/
├── encode.js # CborWriter - CBOR binary encoder
├── decode.js # CborReader - CBOR binary decoder
├── index.js # Main exports
├── diagnostic/ # Diagnostic notation codec
│ ├── encode.js # CBOR bytes → diagnostic string
│ ├── decode.js # Diagnostic string → JavaScript values
│ ├── util.js # Hex conversion and comparison helpers
│ └── index.js # Diagnostic codec exports
└── README.md # This file
```

## Features

- **RFC 8949 Compliant**: Output can be parsed by any standard CBOR decoder
- **Canonical Encoding**: Deterministic output suitable for signature verification
- **Diagnostic Notation**: Human-readable text codec for testing and debugging
- **Full Type Support**: All OCapN types including integers (bignum), floats, strings, symbols, records, and tagged values

## Usage

```javascript
import { makeCborWriter, makeCborReader, cborToDiagnostic } from './index.js';

// Encoding
const writer = makeCborWriter();
writer.writeArrayHeader(3);
writer.writeSelectorFromString('op:deliver');
writer.writeInteger(42n);
writer.writeString('hello');
const bytes = writer.getBytes();

// View as diagnostic notation
console.log(cborToDiagnostic(bytes));
// Output: [280("op:deliver"), 2(h'2a'), "hello"]

// Decoding
const reader = makeCborReader(bytes);
reader.enterList();
const selector = reader.readSelectorAsString(); // "op:deliver"
const integer = reader.readInteger(); // 42n
const string = reader.readString(); // "hello"
reader.exitList();
```

## Key Differences from Syrup

| Aspect | Syrup | CBOR |
|--------|-------|------|
| Format | Text-based delimiters | Binary length prefixes |
| Integers | ASCII digits with sign | Tag 2/3 bignums |
| Symbols | `len'content` | Tag 280 + text |
| Records | `<selector body>` | Tag 27 + array |
| Parsers | Custom only | Standard CBOR libraries |

## Diagnostic Notation Codec

The `diagnostic/` subdirectory provides a text-based codec for CBOR data
using RFC 8949 Appendix G diagnostic notation. It stands on equal footing
with the binary CBOR codec:

- **Encode**: CBOR bytes → diagnostic string
- **Decode**: Diagnostic string → JavaScript values

This is useful for:

- Writing test cases in readable form
- Debugging encoding issues
- Validating interoperability with other implementations

```javascript
import { encode, decode, hexToBytes } from './diagnostic/index.js';

// Encode: CBOR bytes → diagnostic string
const bytes = hexToBytes('d90118666d6574686f64');
console.log(encode(bytes)); // 280("method")

// Decode: diagnostic string → JavaScript values
const value = decode('280("method")');
// { _tag: 280n, _content: "method" }
```

Or use the convenience re-exports from the main index:

```javascript
import { diagnosticEncode, diagnosticDecode, cborToDiagnostic } from './index.js';
```

## Testing

The tests validate:

1. **Round-trip**: encode → decode returns original value
2. **Interoperability**: output parses correctly with the `cbor` npm package
3. **Canonical encoding**: NaN, integers, and other values use canonical form
4. **Diagnostic notation**: human-readable output matches expected format

Run tests with:

```bash
yarn test test/cbor/
```

## Specification

See `docs/cbor-encoding.md` for the complete encoding specification.


Loading
Loading