io-v2-protocol.ts (3536B)
1 // Inert wire decoding, not execution, resolution or a capability declaration. 2 // The caller retains the action and returned borrowed handles across collection. 3 export type Node<H> = { kind: 'leaf' } | { kind: 'stem'; child: H } | { kind: 'fork'; left: H; right: H }; 4 export interface Reader<H> { observe(handle: H): { node: Node<H> }; } 5 export type Action<H> = { kind: 'core'; code: number; payload: H } 6 | { kind: 'request'; group: string; revision: number; operation: string; arguments: H }; 7 8 export function canonicalNat<H>(reader: Reader<H>, handle: H, bits: number): bigint | undefined { 9 if (!Number.isInteger(bits) || bits < 0 || bits > 64) throw new Error('Invalid natural bit bound'); 10 let rest = handle, value = 0n, count = 0, last = 0n; 11 for (;;) { 12 const node = reader.observe(rest).node; 13 if (node.kind === 'leaf') return count === 0 || last === 1n ? value : undefined; 14 if (count === bits || node.kind !== 'fork') return undefined; 15 const bit = reader.observe(node.left).node; 16 if (bit.kind === 'leaf') last = 0n; 17 else if (bit.kind === 'stem' && reader.observe(bit.child).node.kind === 'leaf') last = 1n; 18 else return undefined; 19 value |= last << BigInt(count++); rest = node.right; 20 } 21 } 22 function name<H>(reader: Reader<H>, handle: H): string | undefined { 23 let rest = handle, value = ''; 24 for (;;) { 25 const node = reader.observe(rest).node; 26 if (node.kind === 'leaf') return value || undefined; 27 // Observe only the excess spine cell, never its head or tail. 28 if (value.length === 64 || node.kind !== 'fork') return undefined; 29 const code = canonicalNat(reader, node.left, 7); 30 if (code === undefined || !(code >= 97n && code <= 122n || value.length > 0 && (code >= 48n && code <= 57n || code === 45n))) return undefined; 31 value += String.fromCharCode(Number(code)); rest = node.right; 32 } 33 } 34 export class ProtocolError extends Error {} 35 export function decodeAction<H>(reader: Reader<H>, handle: H): Action<H> { 36 const action = reader.observe(handle).node; 37 if (action.kind !== 'fork') throw new ProtocolError('invalid computation shape'); 38 const code = canonicalNat(reader, action.left, 8); 39 if (code !== undefined && code <= 9n) return { kind: 'core', code: Number(code), payload: action.right }; 40 if (code !== 10n) throw new ProtocolError('unknown computation constructor'); 41 const group = reader.observe(action.right).node; 42 if (group.kind !== 'fork') throw new ProtocolError('invalid facility header'); 43 const revision = reader.observe(group.right).node; 44 if (revision.kind !== 'fork') throw new ProtocolError('invalid facility header'); 45 const operation = reader.observe(revision.right).node; 46 if (operation.kind !== 'fork') throw new ProtocolError('invalid facility header'); 47 const g = name(reader, group.left); 48 if (g === undefined) throw new ProtocolError('invalid facility header'); 49 const v = canonicalNat(reader, revision.left, 16); 50 if (v === undefined || v === 0n) throw new ProtocolError('invalid facility header'); 51 const op = name(reader, operation.left); 52 if (op === undefined) throw new ProtocolError('invalid facility header'); 53 return { kind: 'request', group: g, revision: Number(v), operation: op, arguments: operation.right }; 54 } 55 export function header<H>(reader: Reader<H>, handle: H): object { 56 const action = decodeAction(reader, handle); 57 return action.kind === 'core' ? { kind: action.kind, code: action.code } 58 : { kind: action.kind, group: action.group, revision: action.revision, operation: action.operation }; 59 }