diff --git a/docs/protocol-wire-ids.md b/docs/protocol-wire-ids.md new file mode 100644 index 00000000..5e1f5d0b --- /dev/null +++ b/docs/protocol-wire-ids.md @@ -0,0 +1,145 @@ +# Protocol wire-tag IDs + +> Generated by `scripts/protocol-wire-ids.mjs`. Do not edit by hand — re-run the script. + +- Date: 2026-05-27 +- Source: `packages/host-api/src/protocol/impl.ts` (variant order) + `packages/host-api/src/protocol/messageCodec.ts` (flattening) +- Methods: 54 (43 request, 11 subscribe) +- Variants (wire tags): **130** of max **256** +- Remaining capacity: **126** variants before the single-byte discriminant overflows + +The discriminant is one byte; index `255` is the last representable value. + +| Index (dec) | Index (hex) | Wire tag | Method kind | +| ----------: | ----------- | -------- | ----------- | +| 0 | 0x00 | `host_handshake_request` | request | +| 1 | 0x01 | `host_handshake_response` | request | +| 2 | 0x02 | `host_feature_supported_request` | request | +| 3 | 0x03 | `host_feature_supported_response` | request | +| 4 | 0x04 | `host_push_notification_request` | request | +| 5 | 0x05 | `host_push_notification_response` | request | +| 6 | 0x06 | `host_navigate_to_request` | request | +| 7 | 0x07 | `host_navigate_to_response` | request | +| 8 | 0x08 | `host_device_permission_request` | request | +| 9 | 0x09 | `host_device_permission_response` | request | +| 10 | 0x0A | `remote_permission_request` | request | +| 11 | 0x0B | `remote_permission_response` | request | +| 12 | 0x0C | `host_local_storage_read_request` | request | +| 13 | 0x0D | `host_local_storage_read_response` | request | +| 14 | 0x0E | `host_local_storage_write_request` | request | +| 15 | 0x0F | `host_local_storage_write_response` | request | +| 16 | 0x10 | `host_local_storage_clear_request` | request | +| 17 | 0x11 | `host_local_storage_clear_response` | request | +| 18 | 0x12 | `host_account_connection_status_subscribe_start` | subscribe | +| 19 | 0x13 | `host_account_connection_status_subscribe_stop` | subscribe | +| 20 | 0x14 | `host_account_connection_status_subscribe_interrupt` | subscribe | +| 21 | 0x15 | `host_account_connection_status_subscribe_receive` | subscribe | +| 22 | 0x16 | `host_account_get_request` | request | +| 23 | 0x17 | `host_account_get_response` | request | +| 24 | 0x18 | `host_account_get_alias_request` | request | +| 25 | 0x19 | `host_account_get_alias_response` | request | +| 26 | 0x1A | `host_account_create_proof_request` | request | +| 27 | 0x1B | `host_account_create_proof_response` | request | +| 28 | 0x1C | `host_get_legacy_accounts_request` | request | +| 29 | 0x1D | `host_get_legacy_accounts_response` | request | +| 30 | 0x1E | `host_create_transaction_request` | request | +| 31 | 0x1F | `host_create_transaction_response` | request | +| 32 | 0x20 | `host_create_transaction_with_legacy_account_request` | request | +| 33 | 0x21 | `host_create_transaction_with_legacy_account_response` | request | +| 34 | 0x22 | `host_sign_raw_with_legacy_account_request` | request | +| 35 | 0x23 | `host_sign_raw_with_legacy_account_response` | request | +| 36 | 0x24 | `host_sign_payload_with_legacy_account_request` | request | +| 37 | 0x25 | `host_sign_payload_with_legacy_account_response` | request | +| 38 | 0x26 | `host_chat_create_room_request` | request | +| 39 | 0x27 | `host_chat_create_room_response` | request | +| 40 | 0x28 | `host_chat_register_bot_request` | request | +| 41 | 0x29 | `host_chat_register_bot_response` | request | +| 42 | 0x2A | `host_chat_list_subscribe_start` | subscribe | +| 43 | 0x2B | `host_chat_list_subscribe_stop` | subscribe | +| 44 | 0x2C | `host_chat_list_subscribe_interrupt` | subscribe | +| 45 | 0x2D | `host_chat_list_subscribe_receive` | subscribe | +| 46 | 0x2E | `host_chat_post_message_request` | request | +| 47 | 0x2F | `host_chat_post_message_response` | request | +| 48 | 0x30 | `host_chat_action_subscribe_start` | subscribe | +| 49 | 0x31 | `host_chat_action_subscribe_stop` | subscribe | +| 50 | 0x32 | `host_chat_action_subscribe_interrupt` | subscribe | +| 51 | 0x33 | `host_chat_action_subscribe_receive` | subscribe | +| 52 | 0x34 | `product_chat_custom_message_render_subscribe_start` | subscribe | +| 53 | 0x35 | `product_chat_custom_message_render_subscribe_stop` | subscribe | +| 54 | 0x36 | `product_chat_custom_message_render_subscribe_interrupt` | subscribe | +| 55 | 0x37 | `product_chat_custom_message_render_subscribe_receive` | subscribe | +| 56 | 0x38 | `remote_statement_store_subscribe_start` | subscribe | +| 57 | 0x39 | `remote_statement_store_subscribe_stop` | subscribe | +| 58 | 0x3A | `remote_statement_store_subscribe_interrupt` | subscribe | +| 59 | 0x3B | `remote_statement_store_subscribe_receive` | subscribe | +| 60 | 0x3C | `remote_statement_store_create_proof_request` | request | +| 61 | 0x3D | `remote_statement_store_create_proof_response` | request | +| 62 | 0x3E | `remote_statement_store_submit_request` | request | +| 63 | 0x3F | `remote_statement_store_submit_response` | request | +| 64 | 0x40 | `remote_preimage_lookup_subscribe_start` | subscribe | +| 65 | 0x41 | `remote_preimage_lookup_subscribe_stop` | subscribe | +| 66 | 0x42 | `remote_preimage_lookup_subscribe_interrupt` | subscribe | +| 67 | 0x43 | `remote_preimage_lookup_subscribe_receive` | subscribe | +| 68 | 0x44 | `remote_preimage_submit_request` | request | +| 69 | 0x45 | `remote_preimage_submit_response` | request | +| 70 | 0x46 | `host_jsonrpc_message_send_request` | request | +| 71 | 0x47 | `host_jsonrpc_message_send_response` | request | +| 72 | 0x48 | `host_jsonrpc_message_subscribe_start` | subscribe | +| 73 | 0x49 | `host_jsonrpc_message_subscribe_stop` | subscribe | +| 74 | 0x4A | `host_jsonrpc_message_subscribe_interrupt` | subscribe | +| 75 | 0x4B | `host_jsonrpc_message_subscribe_receive` | subscribe | +| 76 | 0x4C | `remote_chain_head_follow_subscribe_start` | subscribe | +| 77 | 0x4D | `remote_chain_head_follow_subscribe_stop` | subscribe | +| 78 | 0x4E | `remote_chain_head_follow_subscribe_interrupt` | subscribe | +| 79 | 0x4F | `remote_chain_head_follow_subscribe_receive` | subscribe | +| 80 | 0x50 | `remote_chain_head_header_request` | request | +| 81 | 0x51 | `remote_chain_head_header_response` | request | +| 82 | 0x52 | `remote_chain_head_body_request` | request | +| 83 | 0x53 | `remote_chain_head_body_response` | request | +| 84 | 0x54 | `remote_chain_head_storage_request` | request | +| 85 | 0x55 | `remote_chain_head_storage_response` | request | +| 86 | 0x56 | `remote_chain_head_call_request` | request | +| 87 | 0x57 | `remote_chain_head_call_response` | request | +| 88 | 0x58 | `remote_chain_head_unpin_request` | request | +| 89 | 0x59 | `remote_chain_head_unpin_response` | request | +| 90 | 0x5A | `remote_chain_head_continue_request` | request | +| 91 | 0x5B | `remote_chain_head_continue_response` | request | +| 92 | 0x5C | `remote_chain_head_stop_operation_request` | request | +| 93 | 0x5D | `remote_chain_head_stop_operation_response` | request | +| 94 | 0x5E | `remote_chain_spec_genesis_hash_request` | request | +| 95 | 0x5F | `remote_chain_spec_genesis_hash_response` | request | +| 96 | 0x60 | `remote_chain_spec_chain_name_request` | request | +| 97 | 0x61 | `remote_chain_spec_chain_name_response` | request | +| 98 | 0x62 | `remote_chain_spec_properties_request` | request | +| 99 | 0x63 | `remote_chain_spec_properties_response` | request | +| 100 | 0x64 | `remote_chain_transaction_broadcast_request` | request | +| 101 | 0x65 | `remote_chain_transaction_broadcast_response` | request | +| 102 | 0x66 | `remote_chain_transaction_stop_request` | request | +| 103 | 0x67 | `remote_chain_transaction_stop_response` | request | +| 104 | 0x68 | `host_theme_subscribe_start` | subscribe | +| 105 | 0x69 | `host_theme_subscribe_stop` | subscribe | +| 106 | 0x6A | `host_theme_subscribe_interrupt` | subscribe | +| 107 | 0x6B | `host_theme_subscribe_receive` | subscribe | +| 108 | 0x6C | `host_derive_entropy_request` | request | +| 109 | 0x6D | `host_derive_entropy_response` | request | +| 110 | 0x6E | `host_get_user_id_request` | request | +| 111 | 0x6F | `host_get_user_id_response` | request | +| 112 | 0x70 | `host_request_login_request` | request | +| 113 | 0x71 | `host_request_login_response` | request | +| 114 | 0x72 | `host_sign_raw_request` | request | +| 115 | 0x73 | `host_sign_raw_response` | request | +| 116 | 0x74 | `host_sign_payload_request` | request | +| 117 | 0x75 | `host_sign_payload_response` | request | +| 118 | 0x76 | `host_payment_balance_subscribe_start` | subscribe | +| 119 | 0x77 | `host_payment_balance_subscribe_stop` | subscribe | +| 120 | 0x78 | `host_payment_balance_subscribe_interrupt` | subscribe | +| 121 | 0x79 | `host_payment_balance_subscribe_receive` | subscribe | +| 122 | 0x7A | `host_payment_top_up_request` | request | +| 123 | 0x7B | `host_payment_top_up_response` | request | +| 124 | 0x7C | `host_payment_request_request` | request | +| 125 | 0x7D | `host_payment_request_response` | request | +| 126 | 0x7E | `host_payment_status_subscribe_start` | subscribe | +| 127 | 0x7F | `host_payment_status_subscribe_stop` | subscribe | +| 128 | 0x80 | `host_payment_status_subscribe_interrupt` | subscribe | +| 129 | 0x81 | `host_payment_status_subscribe_receive` | subscribe | + diff --git a/scripts/protocol-wire-ids.mjs b/scripts/protocol-wire-ids.mjs new file mode 100755 index 00000000..8ac16509 --- /dev/null +++ b/scripts/protocol-wire-ids.mjs @@ -0,0 +1,134 @@ +#!/usr/bin/env node +// Reports the wire-tag index (SCALE enum discriminant) of every variant in the +// host <-> product protocol enum. +// +// The enum is built in packages/host-api/src/protocol/messageCodec.ts by walking +// `hostApiProtocol` (impl.ts) in declaration order and flattening each method into +// variants. scale-ts `Enum` assigns indices by insertion order, starting at 0. +// This script reproduces that flattening from source, so it needs no build step +// and no node_modules. +// +// Flattening rule (mirrors messageCodec.ts createPayload): +// request -> _request, _response +// subscribe -> _start, _stop, _interrupt, _receive +// +// The discriminant is a single byte, so the enum is capped at 256 variants +// (indices 0..=255). See docs note generated alongside this report. +// +// Usage: +// node scripts/protocol-wire-ids.mjs # print report to stdout +// node scripts/protocol-wire-ids.mjs -o FILE # also write report to FILE +// node scripts/protocol-wire-ids.mjs --json # emit JSON instead of markdown + +import { readFileSync, writeFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join, relative } from 'node:path'; + +const MAX_VARIANTS = 256; // single-byte discriminant + +const here = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(here, '..'); +const implPath = join(repoRoot, 'packages/host-api/src/protocol/impl.ts'); + +const VARIANTS_BY_KIND = { + versionedRequest: ['request', 'response'], + versionedSubscription: ['start', 'stop', 'interrupt', 'receive'], +}; + +/** Parse impl.ts and return the ordered list of { method, kind } entries. */ +function parseProtocol(source) { + // Matches lines like: remote_chain_head_follow_subscribe: versionedSubscription({ + const re = /^\s*([a-z][a-z0-9_]*)\s*:\s*(versionedRequest|versionedSubscription)\s*\(/gm; + const methods = []; + let m; + while ((m = re.exec(source)) !== null) { + methods.push({ method: m[1], kind: m[2] }); + } + if (methods.length === 0) { + throw new Error(`No protocol methods found in ${implPath} — has the format changed?`); + } + return methods; +} + +/** Expand methods into the flat, ordered list of enum variant tags. */ +function buildVariants(methods) { + const variants = []; + for (const { method, kind } of methods) { + const suffixes = VARIANTS_BY_KIND[kind]; + for (const suffix of suffixes) { + variants.push({ tag: `${method}_${suffix}`, method, kind }); + } + } + return variants.map((v, index) => ({ index, ...v })); +} + +function renderMarkdown(variants, methods) { + const total = variants.length; + const remaining = MAX_VARIANTS - total; + const requests = methods.filter((m) => m.kind === 'versionedRequest').length; + const subscriptions = methods.filter((m) => m.kind === 'versionedSubscription').length; + const now = new Date().toISOString().slice(0, 10); + + const lines = []; + lines.push('# Protocol wire-tag IDs'); + lines.push(''); + lines.push('> Generated by `scripts/protocol-wire-ids.mjs`. Do not edit by hand — re-run the script.'); + lines.push(''); + lines.push(`- Date: ${now}`); + lines.push(`- Source: \`${relative(repoRoot, implPath)}\` (variant order) + \`packages/host-api/src/protocol/messageCodec.ts\` (flattening)`); + lines.push(`- Methods: ${methods.length} (${requests} request, ${subscriptions} subscribe)`); + lines.push(`- Variants (wire tags): **${total}** of max **${MAX_VARIANTS}**`); + lines.push(`- Remaining capacity: **${remaining}** variants before the single-byte discriminant overflows`); + lines.push(''); + lines.push('The discriminant is one byte; index `255` is the last representable value.'); + lines.push(''); + lines.push('| Index (dec) | Index (hex) | Wire tag | Method kind |'); + lines.push('| ----------: | ----------- | -------- | ----------- |'); + for (const v of variants) { + const hex = '0x' + v.index.toString(16).toUpperCase().padStart(2, '0'); + const kind = v.kind === 'versionedRequest' ? 'request' : 'subscribe'; + lines.push(`| ${v.index} | ${hex} | \`${v.tag}\` | ${kind} |`); + } + lines.push(''); + return lines.join('\n'); +} + +function main() { + const args = process.argv.slice(2); + const asJson = args.includes('--json'); + const outIdx = args.findIndex((a) => a === '-o' || a === '--out'); + const outFile = outIdx !== -1 ? args[outIdx + 1] : undefined; + + const source = readFileSync(implPath, 'utf8'); + const methods = parseProtocol(source); + const variants = buildVariants(methods); + + if (variants.length > MAX_VARIANTS) { + console.error( + `WARNING: ${variants.length} variants exceeds the ${MAX_VARIANTS}-variant single-byte limit.`, + ); + } + + const output = asJson + ? JSON.stringify( + { + generatedAt: new Date().toISOString(), + maxVariants: MAX_VARIANTS, + total: variants.length, + remaining: MAX_VARIANTS - variants.length, + variants, + }, + null, + 2, + ) + : renderMarkdown(variants, methods); + + process.stdout.write(output + '\n'); + if (outFile) { + const target = outFile.startsWith('/') ? outFile : join(process.cwd(), outFile); + writeFileSync(target, output + '\n'); + console.error(`Wrote report to ${target}`); + } +} + +main();