From ae279acfbee8c4cef6172079f08f7e5a22f22d2b Mon Sep 17 00:00:00 2001 From: Nidish Date: Fri, 7 Aug 2026 11:59:31 +0530 Subject: [PATCH] feat(wire): split the frame discriminant into trait and method bytes --- docs/design/truapi-protocol.md | 206 ++++- js/packages/truapi/src/client.test.ts | 64 +- js/packages/truapi/src/client.ts | 32 +- js/packages/truapi/src/sandbox.test.ts | 4 +- js/packages/truapi/src/transport.ts | 63 +- js/packages/truapi/src/wire-equality.test.ts | 93 +- js/packages/truapi/src/wire-table.test.ts | 48 +- rust/crates/truapi-codegen/src/main.rs | 2 +- rust/crates/truapi-codegen/src/rust.rs | 109 ++- .../truapi-codegen/src/rust/wire_table.rs | 115 ++- rust/crates/truapi-codegen/src/rustdoc.rs | 37 +- rust/crates/truapi-codegen/src/ts.rs | 141 ++- .../truapi-codegen/tests/golden/wire_table.rs | 858 ++++++++++-------- rust/crates/truapi-macros/src/lib.rs | 65 +- rust/crates/truapi-server/src/core.rs | 18 +- rust/crates/truapi-server/src/dispatcher.rs | 87 +- rust/crates/truapi-server/src/frame.rs | 173 ++-- .../truapi-server/src/generated/wire_table.rs | 858 ++++++++++-------- rust/crates/truapi-server/src/host_core.rs | 3 +- rust/crates/truapi-server/src/native.rs | 18 +- rust/crates/truapi-server/src/subscription.rs | 43 +- rust/crates/truapi-server/src/ws_bridge.rs | 6 +- .../truapi-server/tests/golden_frame.rs | 8 +- .../tests/snapshots/golden-account-get.bin | Bin 14 -> 15 bytes .../truapi-server/tests/wire_result_shape.rs | 51 +- .../tests/wire_table_ts_parity.rs | 20 +- rust/crates/truapi/README.md | 2 +- rust/crates/truapi/src/api/account.rs | 19 +- rust/crates/truapi/src/api/chain.rs | 29 +- rust/crates/truapi/src/api/chat.rs | 15 +- rust/crates/truapi/src/api/coin_payment.rs | 21 +- rust/crates/truapi/src/api/entropy.rs | 5 +- rust/crates/truapi/src/api/local_storage.rs | 9 +- rust/crates/truapi/src/api/notifications.rs | 7 +- rust/crates/truapi/src/api/payment.rs | 11 +- rust/crates/truapi/src/api/permissions.rs | 7 +- rust/crates/truapi/src/api/preimage.rs | 7 +- .../truapi/src/api/resource_allocation.rs | 5 +- rust/crates/truapi/src/api/signing.rs | 15 +- rust/crates/truapi/src/api/statement_store.rs | 11 +- rust/crates/truapi/src/api/system.rs | 7 +- rust/crates/truapi/src/api/theme.rs | 5 +- rust/crates/truapi/src/lib.rs | 2 +- scripts/codegen.sh | 4 +- 44 files changed, 2148 insertions(+), 1155 deletions(-) diff --git a/docs/design/truapi-protocol.md b/docs/design/truapi-protocol.md index e2e2d4a90..58156013e 100644 --- a/docs/design/truapi-protocol.md +++ b/docs/design/truapi-protocol.md @@ -49,13 +49,19 @@ struct Message { } ``` -`requestId` ties related messages together (see [Rules](#rules)); `payload` carries the action itself. `Payload` is an enum whose variants are the **actions** — the individual things a Host and Product can say to each other. +`requestId` ties related messages together (see [Rules](#rules)); `payload` carries the action itself. On the wire the envelope is laid out as: + +```text +[requestId: SCALE str][trait: u8][method: u8][payload bytes...] +``` + +The two bytes after the `requestId` are the **`(trait, method)` discriminant pair**. The first byte identifies the API trait (`System`, `Account`, `Chain`, ...); the second identifies the action within that trait. The payload bytes are the SCALE-encoded action value, inlined without a length prefix — the receiver reads to the end of the transport frame. Conceptually, `Payload` is a per-trait enum whose variants are the **actions** — the individual things a Host and Product can say to each other. Actions are not written by hand. They are derived mechanically from the TrUAPI methods, so the high-level method signature and the wire format can never drift apart. One method expands into several actions depending on its shape: a plain call becomes a request/response pair, while a subscription becomes a small lifecycle of start, stop, interrupt, and receive messages. -Each action variant carries an explicit wire-protocol discriminant — its `request_id`, `response_id`, `start_id`, `stop_id`, `interrupt_id`, or `receive_id`. These ids are assigned per method in the `truapi` crate via the `#[wire(...)]` annotation. They are **append-only and never reused**: once an id ships it keeps its meaning forever, which is what lets a newer Host and an older Product still understand each other. The crate is the source of truth for their values. +Trait discriminants are assigned per trait in the `truapi` crate via the trait-level `#[wire_trait(id = N)]` annotation, with the `System` trait fixed at `0` (so a handshake request frame always starts `[requestId][0x00][0x00]`). Each action carries an explicit method discriminant within its trait — its `request_id`, `response_id`, `start_id`, `stop_id`, `interrupt_id`, or `receive_id` — assigned per method via the `#[wire(...)]` annotation and numbered from `0` independently inside every trait. Ids are **append-only per trait and never reused**: once a `(trait, method)` pair ships it keeps its meaning forever, which is what lets a newer Host and an older Product still understand each other, and adding methods to one trait never disturbs the ids of any other trait. The crate is the source of truth for all values. -Payloads are versioned independently of the action id, so a single message can evolve without renumbering anything around it. The current version `V1` encodes as discriminant `0`: +Payloads are versioned independently of the discriminant pair, so a single message can evolve without renumbering anything around it. The current version `V1` encodes as discriminant `0`: ```rust enum Versioned { @@ -93,7 +99,7 @@ Actions are derived from the TrUAPI methods using the following algorithm: - Argument: the versioned callback argument `Versioned` - Discriminant: `receive_id` -Put together, a slice of `Payload` looks like this (the payload types are illustrative; see the `truapi` crate for the real ones): +Put together, a slice of one trait's `Payload` actions looks like this (the payload types are illustrative; see the `truapi` crate for the real ones): ```rust enum Payload { @@ -149,6 +155,196 @@ Before either side trusts a single byte of payload, they have to agree on how th Handshake calls are bidirectional: both Host and Product can send a handshake request, and both MUST respond to one. An implementation CAN apply a timeout of 10 seconds, after which the connection is marked failed and the call returns a timeout error. The handshake result can be cached. -The handshake request carries the protocol (codec) version as a `u8`. On receiving it, the peer switches its encoding/decoding mode to match; for SCALE codec, the version is `1`. A successful handshake MUST be the first request TrUAPI processes — any other request sent before a successful handshake response MUST fail. +The handshake request carries the protocol (codec) version as a `u8`. On receiving it, the peer switches its encoding/decoding mode to match; for the SCALE codec with the two-byte `(trait, method)` envelope, the version is `2`. (Codec version `1` designates the retired single-byte-discriminant envelope; a peer speaking it fails the handshake.) A successful handshake MUST be the first request TrUAPI processes — any other request sent before a successful handshake response MUST fail. The concrete handshake request, response, and error types are defined in the `truapi` crate. + + +## Appendix: codec-1 → codec-2 discriminant mapping + +Codec version 1 used a single flat `u8` discriminant shared across all traits. Codec version 2 replaces it with the `(trait, method)` pair. This table is the one-time mapping between the two numberings; it exists only to interpret captured codec-1 traffic and old fixtures, and is never extended — new methods only ever get codec-2 pairs. + +Trait id assignment: + +| Trait | Trait id | +| --- | --- | +| `System` | 0 | +| `Account` | 1 | +| `Chain` | 2 | +| `Chat` | 3 | +| `CoinPayment` | 4 | +| `Entropy` | 5 | +| `LocalStorage` | 6 | +| `Notifications` | 7 | +| `Payment` | 8 | +| `Permissions` | 9 | +| `Preimage` | 10 | +| `ResourceAllocation` | 11 | +| `Signing` | 12 | +| `StatementStore` | 13 | +| `Theme` | 14 | + +Per-action mapping (codec-1 flat id → codec-2 `(trait, method)` pair): + +| Action | Codec-1 id | Codec-2 (trait, method) | +| --- | --- | --- | +| `system_handshake_request` | 0 | (0, 0) | +| `system_handshake_response` | 1 | (0, 1) | +| `system_feature_supported_request` | 2 | (0, 2) | +| `system_feature_supported_response` | 3 | (0, 3) | +| `system_navigate_to_request` | 6 | (0, 4) | +| `system_navigate_to_response` | 7 | (0, 5) | +| `account_connection_status_subscribe_start` | 18 | (1, 0) | +| `account_connection_status_subscribe_stop` | 19 | (1, 1) | +| `account_connection_status_subscribe_interrupt` | 20 | (1, 2) | +| `account_connection_status_subscribe_receive` | 21 | (1, 3) | +| `account_get_account_request` | 22 | (1, 4) | +| `account_get_account_response` | 23 | (1, 5) | +| `account_get_account_alias_request` | 24 | (1, 6) | +| `account_get_account_alias_response` | 25 | (1, 7) | +| `account_create_account_proof_request` | 26 | (1, 8) | +| `account_create_account_proof_response` | 27 | (1, 9) | +| `account_get_legacy_accounts_request` | 28 | (1, 10) | +| `account_get_legacy_accounts_response` | 29 | (1, 11) | +| `account_get_user_id_request` | 110 | (1, 12) | +| `account_get_user_id_response` | 111 | (1, 13) | +| `account_request_login_request` | 112 | (1, 14) | +| `account_request_login_response` | 113 | (1, 15) | +| `account_sign_vrf_request` | 164 | (1, 16) | +| `account_sign_vrf_response` | 165 | (1, 17) | +| `chain_follow_head_subscribe_start` | 76 | (2, 0) | +| `chain_follow_head_subscribe_stop` | 77 | (2, 1) | +| `chain_follow_head_subscribe_interrupt` | 78 | (2, 2) | +| `chain_follow_head_subscribe_receive` | 79 | (2, 3) | +| `chain_get_head_header_request` | 80 | (2, 4) | +| `chain_get_head_header_response` | 81 | (2, 5) | +| `chain_get_head_body_request` | 82 | (2, 6) | +| `chain_get_head_body_response` | 83 | (2, 7) | +| `chain_get_head_storage_request` | 84 | (2, 8) | +| `chain_get_head_storage_response` | 85 | (2, 9) | +| `chain_call_head_request` | 86 | (2, 10) | +| `chain_call_head_response` | 87 | (2, 11) | +| `chain_unpin_head_request` | 88 | (2, 12) | +| `chain_unpin_head_response` | 89 | (2, 13) | +| `chain_continue_head_request` | 90 | (2, 14) | +| `chain_continue_head_response` | 91 | (2, 15) | +| `chain_stop_head_operation_request` | 92 | (2, 16) | +| `chain_stop_head_operation_response` | 93 | (2, 17) | +| `chain_get_spec_genesis_hash_request` | 94 | (2, 18) | +| `chain_get_spec_genesis_hash_response` | 95 | (2, 19) | +| `chain_get_spec_chain_name_request` | 96 | (2, 20) | +| `chain_get_spec_chain_name_response` | 97 | (2, 21) | +| `chain_get_spec_properties_request` | 98 | (2, 22) | +| `chain_get_spec_properties_response` | 99 | (2, 23) | +| `chain_broadcast_transaction_request` | 100 | (2, 24) | +| `chain_broadcast_transaction_response` | 101 | (2, 25) | +| `chain_stop_transaction_request` | 102 | (2, 26) | +| `chain_stop_transaction_response` | 103 | (2, 27) | +| `chat_create_room_request` | 38 | (3, 0) | +| `chat_create_room_response` | 39 | (3, 1) | +| `chat_register_bot_request` | 40 | (3, 2) | +| `chat_register_bot_response` | 41 | (3, 3) | +| `chat_list_subscribe_start` | 42 | (3, 4) | +| `chat_list_subscribe_stop` | 43 | (3, 5) | +| `chat_list_subscribe_interrupt` | 44 | (3, 6) | +| `chat_list_subscribe_receive` | 45 | (3, 7) | +| `chat_post_message_request` | 46 | (3, 8) | +| `chat_post_message_response` | 47 | (3, 9) | +| `chat_action_subscribe_start` | 48 | (3, 10) | +| `chat_action_subscribe_stop` | 49 | (3, 11) | +| `chat_action_subscribe_interrupt` | 50 | (3, 12) | +| `chat_action_subscribe_receive` | 51 | (3, 13) | +| `chat_custom_message_render_subscribe_start` | 52 | (3, 14) | +| `chat_custom_message_render_subscribe_stop` | 53 | (3, 15) | +| `chat_custom_message_render_subscribe_interrupt` | 54 | (3, 16) | +| `chat_custom_message_render_subscribe_receive` | 55 | (3, 17) | +| `coin_payment_create_purse_request` | 136 | (4, 0) | +| `coin_payment_create_purse_response` | 137 | (4, 1) | +| `coin_payment_query_purse_request` | 138 | (4, 2) | +| `coin_payment_query_purse_response` | 139 | (4, 3) | +| `coin_payment_rebalance_purse_start` | 140 | (4, 4) | +| `coin_payment_rebalance_purse_stop` | 141 | (4, 5) | +| `coin_payment_rebalance_purse_interrupt` | 142 | (4, 6) | +| `coin_payment_rebalance_purse_receive` | 143 | (4, 7) | +| `coin_payment_delete_purse_start` | 144 | (4, 8) | +| `coin_payment_delete_purse_stop` | 145 | (4, 9) | +| `coin_payment_delete_purse_interrupt` | 146 | (4, 10) | +| `coin_payment_delete_purse_receive` | 147 | (4, 11) | +| `coin_payment_create_receivable_request` | 148 | (4, 12) | +| `coin_payment_create_receivable_response` | 149 | (4, 13) | +| `coin_payment_create_cheque_request` | 150 | (4, 14) | +| `coin_payment_create_cheque_response` | 151 | (4, 15) | +| `coin_payment_deposit_start` | 152 | (4, 16) | +| `coin_payment_deposit_stop` | 153 | (4, 17) | +| `coin_payment_deposit_interrupt` | 154 | (4, 18) | +| `coin_payment_deposit_receive` | 155 | (4, 19) | +| `coin_payment_refund_start` | 156 | (4, 20) | +| `coin_payment_refund_stop` | 157 | (4, 21) | +| `coin_payment_refund_interrupt` | 158 | (4, 22) | +| `coin_payment_refund_receive` | 159 | (4, 23) | +| `coin_payment_listen_for_payment_start` | 160 | (4, 24) | +| `coin_payment_listen_for_payment_stop` | 161 | (4, 25) | +| `coin_payment_listen_for_payment_interrupt` | 162 | (4, 26) | +| `coin_payment_listen_for_payment_receive` | 163 | (4, 27) | +| `entropy_derive_request` | 108 | (5, 0) | +| `entropy_derive_response` | 109 | (5, 1) | +| `local_storage_read_request` | 12 | (6, 0) | +| `local_storage_read_response` | 13 | (6, 1) | +| `local_storage_write_request` | 14 | (6, 2) | +| `local_storage_write_response` | 15 | (6, 3) | +| `local_storage_clear_request` | 16 | (6, 4) | +| `local_storage_clear_response` | 17 | (6, 5) | +| `notifications_send_push_notification_request` | 4 | (7, 0) | +| `notifications_send_push_notification_response` | 5 | (7, 1) | +| `notifications_cancel_push_notification_request` | 134 | (7, 2) | +| `notifications_cancel_push_notification_response` | 135 | (7, 3) | +| `payment_balance_subscribe_start` | 118 | (8, 0) | +| `payment_balance_subscribe_stop` | 119 | (8, 1) | +| `payment_balance_subscribe_interrupt` | 120 | (8, 2) | +| `payment_balance_subscribe_receive` | 121 | (8, 3) | +| `payment_top_up_request` | 122 | (8, 4) | +| `payment_top_up_response` | 123 | (8, 5) | +| `payment_request_request` | 124 | (8, 6) | +| `payment_request_response` | 125 | (8, 7) | +| `payment_status_subscribe_start` | 126 | (8, 8) | +| `payment_status_subscribe_stop` | 127 | (8, 9) | +| `payment_status_subscribe_interrupt` | 128 | (8, 10) | +| `payment_status_subscribe_receive` | 129 | (8, 11) | +| `permissions_request_device_permission_request` | 8 | (9, 0) | +| `permissions_request_device_permission_response` | 9 | (9, 1) | +| `permissions_request_remote_permission_request` | 10 | (9, 2) | +| `permissions_request_remote_permission_response` | 11 | (9, 3) | +| `preimage_lookup_subscribe_start` | 64 | (10, 0) | +| `preimage_lookup_subscribe_stop` | 65 | (10, 1) | +| `preimage_lookup_subscribe_interrupt` | 66 | (10, 2) | +| `preimage_lookup_subscribe_receive` | 67 | (10, 3) | +| `preimage_submit_request` | 68 | (10, 4) | +| `preimage_submit_response` | 69 | (10, 5) | +| `resource_allocation_request_request` | 130 | (11, 0) | +| `resource_allocation_request_response` | 131 | (11, 1) | +| `signing_create_transaction_request` | 30 | (12, 0) | +| `signing_create_transaction_response` | 31 | (12, 1) | +| `signing_create_transaction_with_legacy_account_request` | 32 | (12, 2) | +| `signing_create_transaction_with_legacy_account_response` | 33 | (12, 3) | +| `signing_sign_raw_with_legacy_account_request` | 34 | (12, 4) | +| `signing_sign_raw_with_legacy_account_response` | 35 | (12, 5) | +| `signing_sign_payload_with_legacy_account_request` | 36 | (12, 6) | +| `signing_sign_payload_with_legacy_account_response` | 37 | (12, 7) | +| `signing_sign_raw_request` | 114 | (12, 8) | +| `signing_sign_raw_response` | 115 | (12, 9) | +| `signing_sign_payload_request` | 116 | (12, 10) | +| `signing_sign_payload_response` | 117 | (12, 11) | +| `statement_store_subscribe_start` | 56 | (13, 0) | +| `statement_store_subscribe_stop` | 57 | (13, 1) | +| `statement_store_subscribe_interrupt` | 58 | (13, 2) | +| `statement_store_subscribe_receive` | 59 | (13, 3) | +| `statement_store_create_proof_request` | 60 | (13, 4) | +| `statement_store_create_proof_response` | 61 | (13, 5) | +| `statement_store_submit_request` | 62 | (13, 6) | +| `statement_store_submit_response` | 63 | (13, 7) | +| `statement_store_create_proof_authorized_request` | 132 | (13, 8) | +| `statement_store_create_proof_authorized_response` | 133 | (13, 9) | +| `theme_subscribe_start` | 104 | (14, 0) | +| `theme_subscribe_stop` | 105 | (14, 1) | +| `theme_subscribe_interrupt` | 106 | (14, 2) | +| `theme_subscribe_receive` | 107 | (14, 3) | diff --git a/js/packages/truapi/src/client.test.ts b/js/packages/truapi/src/client.test.ts index 300a0c545..2eb228bed 100644 --- a/js/packages/truapi/src/client.test.ts +++ b/js/packages/truapi/src/client.test.ts @@ -101,10 +101,11 @@ describe("generated client transport", () => { void client.account.getAccount(request); const expectedPayload = T.VersionedHostAccountGetRequest.enc({ tag: "V1", value: request }); - const expectedFrame = new Uint8Array(str.enc("p:1").length + 1 + expectedPayload.length); + const expectedFrame = new Uint8Array(str.enc("p:1").length + 2 + expectedPayload.length); expectedFrame.set(str.enc("p:1"), 0); - expectedFrame[str.enc("p:1").length] = 22; - expectedFrame.set(expectedPayload, str.enc("p:1").length + 1); + expectedFrame[str.enc("p:1").length] = 1; // account trait + expectedFrame[str.enc("p:1").length + 1] = 4; // get_account request + expectedFrame.set(expectedPayload, str.enc("p:1").length + 2); expect(toHex(fixture.sent[0])).toBe(toHex(expectedFrame)); }); @@ -118,12 +119,13 @@ describe("generated client transport", () => { const expectedPayload = T.VersionedHostHandshakeRequest.enc({ tag: "V1", - value: { codecVersion: 1 }, + value: { codecVersion: 2 }, }); - const expectedFrame = new Uint8Array(str.enc("p:1").length + 1 + expectedPayload.length); + const expectedFrame = new Uint8Array(str.enc("p:1").length + 2 + expectedPayload.length); expectedFrame.set(str.enc("p:1"), 0); - expectedFrame[str.enc("p:1").length] = 0; - expectedFrame.set(expectedPayload, str.enc("p:1").length + 1); + expectedFrame[str.enc("p:1").length] = 0; // system trait + expectedFrame[str.enc("p:1").length + 1] = 0; // handshake request + expectedFrame.set(expectedPayload, str.enc("p:1").length + 2); expect(toHex(fixture.sent[0])).toBe(toHex(expectedFrame)); }); @@ -138,7 +140,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: "p:1", payload: { - id: W.SYSTEM_HANDSHAKE.response, + traitId: W.SYSTEM_HANDSHAKE.trait, + + methodId: W.SYSTEM_HANDSHAKE.response, value: handshakeResponsePayload({ success: true, value: undefined }), }, }), @@ -163,7 +167,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: "p:1", payload: { - id: W.ACCOUNT_GET_ACCOUNT.response, + traitId: W.ACCOUNT_GET_ACCOUNT.trait, + + methodId: W.ACCOUNT_GET_ACCOUNT.response, value: accountGetResponsePayload({ success: false, value: { tag: "Domain", value: reason }, @@ -185,12 +191,12 @@ describe("generated client transport", () => { const requestPayload = T.VersionedHostHandshakeRequest.enc({ tag: "V1", - value: { codecVersion: 1 }, + value: { codecVersion: 2 }, }); const requestFrame = unwrap( encodeWireMessage({ requestId: "h:1", - payload: { id: W.SYSTEM_HANDSHAKE.request, value: requestPayload }, + payload: { traitId: W.SYSTEM_HANDSHAKE.trait, methodId: W.SYSTEM_HANDSHAKE.request, value: requestPayload }, }), "encode inbound handshake_request", ); @@ -200,7 +206,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: "h:1", payload: { - id: W.SYSTEM_HANDSHAKE.response, + traitId: W.SYSTEM_HANDSHAKE.trait, + + methodId: W.SYSTEM_HANDSHAKE.response, value: handshakeResponsePayload({ success: true, value: undefined }), }, }), @@ -223,7 +231,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: sub.subscriptionId, payload: { - id: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.receive, + traitId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.trait, + + methodId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.receive, value: T.VersionedHostAccountConnectionStatusSubscribeItem.enc({ tag: "V1", value: "Connected", @@ -251,7 +261,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: sub.subscriptionId, payload: { - id: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.interrupt, + traitId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.trait, + + methodId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.interrupt, value: _void.enc(undefined), }, }), @@ -283,7 +295,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: sub.subscriptionId, payload: { - id: W.PAYMENT_BALANCE_SUBSCRIBE.interrupt, + traitId: W.PAYMENT_BALANCE_SUBSCRIBE.trait, + + methodId: W.PAYMENT_BALANCE_SUBSCRIBE.interrupt, value: versionedV1(CallError(T.VersionedHostPaymentBalanceSubscribeError)).enc({ tag: "V1", value: callError, @@ -320,7 +334,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: sub.subscriptionId, payload: { - id: W.COIN_PAYMENT_REBALANCE_PURSE.interrupt, + traitId: W.COIN_PAYMENT_REBALANCE_PURSE.trait, + + methodId: W.COIN_PAYMENT_REBALANCE_PURSE.interrupt, value: versionedV1( CallError(T.VersionedHostCoinPaymentRebalancePurseError), ).enc({ tag: "V1", value: callError }), @@ -353,7 +369,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: sub.subscriptionId, payload: { - id: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.receive, + traitId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.trait, + + methodId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.receive, value: _void.enc(undefined), }, }), @@ -371,7 +389,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: sub.subscriptionId, payload: { - id: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.stop, + traitId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.trait, + + methodId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.stop, value: _void.enc(undefined), }, }), @@ -383,7 +403,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: sub.subscriptionId, payload: { - id: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.receive, + traitId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.trait, + + methodId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.receive, value: T.VersionedHostAccountConnectionStatusSubscribeItem.enc({ tag: "V1", value: "Connected", @@ -414,7 +436,9 @@ describe("generated client transport", () => { encodeWireMessage({ requestId: sub.subscriptionId, payload: { - id: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.stop, + traitId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.trait, + + methodId: W.ACCOUNT_CONNECTION_STATUS_SUBSCRIBE.stop, value: _void.enc(undefined), }, }), diff --git a/js/packages/truapi/src/client.ts b/js/packages/truapi/src/client.ts index f45481f43..44a1145c2 100644 --- a/js/packages/truapi/src/client.ts +++ b/js/packages/truapi/src/client.ts @@ -217,7 +217,10 @@ export function createTransport( } const { requestId, payload } = decoded.value; - if (payload.id === W.SYSTEM_HANDSHAKE.request) { + if ( + payload.traitId === W.SYSTEM_HANDSHAKE.trait && + payload.methodId === W.SYSTEM_HANDSHAKE.request + ) { // Auto-respond to inbound `host_handshake_request` frames. // // Legacy hosts shipping `@novasamatech/host-api@0.6.x` (e.g. dotli) @@ -247,7 +250,8 @@ export function createTransport( send({ requestId, payload: { - id: W.SYSTEM_HANDSHAKE.response, + traitId: W.SYSTEM_HANDSHAKE.trait, + methodId: W.SYSTEM_HANDSHAKE.response, value: response, }, }); @@ -259,7 +263,10 @@ export function createTransport( const p = pending.get(requestId); if (p) { - if (payload.id !== p.ids.response) { + if ( + payload.traitId !== p.ids.trait || + payload.methodId !== p.ids.response + ) { return; } pending.delete(requestId); @@ -273,7 +280,10 @@ export function createTransport( const subscription = subscriptions.get(requestId); if (subscription) { - if (payload.id === subscription.ids.receive) { + if ( + payload.traitId === subscription.ids.trait && + payload.methodId === subscription.ids.receive + ) { try { subscription.onReceive(payload.value); } catch (error) { @@ -284,7 +294,10 @@ export function createTransport( subscriptions.delete(requestId); subscription.onClose?.(toError(error)); } - } else if (payload.id === subscription.ids.interrupt) { + } else if ( + payload.traitId === subscription.ids.trait && + payload.methodId === subscription.ids.interrupt + ) { subscriptions.delete(requestId); subscription.onInterrupt?.(payload.value); } @@ -340,7 +353,8 @@ export function createTransport( send({ requestId, payload: { - id: ids.request, + traitId: ids.trait, + methodId: ids.request, value: payload, }, }); @@ -381,7 +395,8 @@ export function createTransport( send({ requestId, payload: { - id: ids.start, + traitId: ids.trait, + methodId: ids.start, value: payload, }, }); @@ -401,7 +416,8 @@ export function createTransport( send({ requestId, payload: { - id: ids.stop, + traitId: ids.trait, + methodId: ids.stop, value: _void.enc(undefined), }, }); diff --git a/js/packages/truapi/src/sandbox.test.ts b/js/packages/truapi/src/sandbox.test.ts index 18daa35fa..1f87c0aa9 100644 --- a/js/packages/truapi/src/sandbox.test.ts +++ b/js/packages/truapi/src/sandbox.test.ts @@ -213,7 +213,7 @@ describe("sandbox iframe MessagePort handshake", () => { const probe = encodeWireMessage({ requestId: "legacy-probe", - payload: { id: 255, value: new Uint8Array() }, + payload: { traitId: 255, methodId: 255, value: new Uint8Array() }, }); expect(probe.isOk()).toBe(true); if (probe.isErr()) throw probe.error; @@ -247,7 +247,7 @@ describe("sandbox iframe MessagePort handshake", () => { const probe = encodeWireMessage({ requestId: "legacy-probe", - payload: { id: 255, value: new Uint8Array() }, + payload: { traitId: 255, methodId: 255, value: new Uint8Array() }, }); expect(probe.isOk()).toBe(true); if (probe.isErr()) throw probe.error; diff --git a/js/packages/truapi/src/transport.ts b/js/packages/truapi/src/transport.ts index 926ec77b0..412baa737 100644 --- a/js/packages/truapi/src/transport.ts +++ b/js/packages/truapi/src/transport.ts @@ -110,12 +110,17 @@ export interface ObservableLike { **/ export interface RequestFrameIds { /** - * Wire discriminant for the outbound request frame. + * Wire trait discriminant carried by both frames. + **/ + trait: number; + + /** + * Wire method discriminant for the outbound request frame. **/ request: number; /** - * Wire discriminant for the inbound response frame. + * Wire method discriminant for the inbound response frame. **/ response: number; } @@ -125,22 +130,27 @@ export interface RequestFrameIds { **/ export interface SubscriptionFrameIds { /** - * Wire discriminant for the outbound start frame. + * Wire trait discriminant carried by all four frames. + **/ + trait: number; + + /** + * Wire method discriminant for the outbound start frame. **/ start: number; /** - * Wire discriminant for the outbound stop frame. + * Wire method discriminant for the outbound stop frame. **/ stop: number; /** - * Wire discriminant for the inbound interrupt frame. + * Wire method discriminant for the inbound interrupt frame. **/ interrupt: number; /** - * Wire discriminant for the inbound receive frame. + * Wire method discriminant for the inbound receive frame. **/ receive: number; } @@ -236,9 +246,14 @@ export interface TrUApiTransport { **/ export interface Payload { /** - * Wire-table numeric discriminant. + * Wire-table trait discriminant: first byte of the `(trait, method)` pair. **/ - id: number; + traitId: number; + + /** + * Wire-table method discriminant within the trait: second byte of the pair. + **/ + methodId: number; /** * SCALE-encoded payload body. @@ -298,12 +313,20 @@ export interface WireProvider { export function encodeWireMessage( message: ProtocolMessage, ): Result { - const id = message.payload.id; - if (!Number.isInteger(id) || id < 0 || id > 255) { - return err(new Error(`Invalid wire discriminant: ${id}`)); + const { traitId, methodId } = message.payload; + if (!Number.isInteger(traitId) || traitId < 0 || traitId > 255) { + return err(new Error(`Invalid wire trait discriminant: ${traitId}`)); + } + if (!Number.isInteger(methodId) || methodId < 0 || methodId > 255) { + return err(new Error(`Invalid wire method discriminant: ${methodId}`)); } return ok( - concatBytes(str.enc(message.requestId), u8.enc(id), message.payload.value), + concatBytes( + str.enc(message.requestId), + u8.enc(traitId), + u8.enc(methodId), + message.payload.value, + ), ); } @@ -325,15 +348,23 @@ export function decodeWireMessage( const requestId = str.dec(cursor.subarray(0, requestIdEnd)); cursor = cursor.subarray(requestIdEnd); if (cursor.length < 1) { - return err(new Error("Wire frame too short: missing discriminant byte")); + return err( + new Error("Wire frame too short: missing trait discriminant byte"), + ); + } + if (cursor.length < 2) { + return err( + new Error("Wire frame too short: missing method discriminant byte"), + ); } - const id = cursor[0]; - const value = cursor.subarray(1); + const traitId = cursor[0]; + const methodId = cursor[1]; + const value = cursor.subarray(2); // Hand the value bytes back as a fresh slice so callers may safely retain // it even if the source buffer is reused by the transport. const valueCopy = new Uint8Array(value.length); valueCopy.set(value); - return ok({ requestId, payload: { id, value: valueCopy } }); + return ok({ requestId, payload: { traitId, methodId, value: valueCopy } }); } /** diff --git a/js/packages/truapi/src/wire-equality.test.ts b/js/packages/truapi/src/wire-equality.test.ts index af656c48f..5aa37a050 100644 --- a/js/packages/truapi/src/wire-equality.test.ts +++ b/js/packages/truapi/src/wire-equality.test.ts @@ -18,12 +18,13 @@ function toHex(u: Uint8Array): string { .join(""); } -function expectedWire(tagId: number, valueBytes: Uint8Array): Uint8Array { +function expectedWire(traitId: number, methodId: number, valueBytes: Uint8Array): Uint8Array { const reqId = str.enc("p:1"); - const out = new Uint8Array(reqId.length + 1 + valueBytes.length); + const out = new Uint8Array(reqId.length + 2 + valueBytes.length); out.set(reqId, 0); - out[reqId.length] = tagId; - out.set(valueBytes, reqId.length + 1); + out[reqId.length] = traitId; + out[reqId.length + 1] = methodId; + out.set(valueBytes, reqId.length + 2); return out; } @@ -38,19 +39,36 @@ function unwrap(result: Result, message: string): T { } describe("encodeWireMessage / decodeWireMessage wire equality", () => { - it("encodes handshake_request (discriminant 0) to match the Rust reference", () => { - const inner = new Uint8Array([0x00, 0x01]); // V1 variant + codec_version=1 + it("pins the handshake frame end-to-end: requestId + 0x00 0x00 + payload", () => { + // Trait 0 = system, method 0 = handshake request. This locks the + // system trait to discriminant zero: the handshake is the first frame + // either side sends, so its envelope must never drift. + expect(W.SYSTEM_HANDSHAKE.trait).toBe(0); + expect(W.SYSTEM_HANDSHAKE.request).toBe(0); + + const inner = new Uint8Array([0x00, 0x02]); // V1 variant + codec_version=2 const encoded = unwrap( encodeWireMessage({ requestId: "p:1", - payload: { id: W.SYSTEM_HANDSHAKE.request, value: inner }, + payload: { + traitId: W.SYSTEM_HANDSHAKE.trait, + methodId: W.SYSTEM_HANDSHAKE.request, + value: inner, + }, }), "encode handshake_request", ); - expect(toHex(encoded)).toBe(toHex(expectedWire(0, inner))); + // [0c 70 3a 31] "p:1" + [00] system trait + [00] handshake request + payload. + expect(toHex(encoded)).toBe("0c703a3100000002"); + expect(toHex(encoded)).toBe(toHex(expectedWire(0, 0, inner))); + + const decoded = unwrap(decodeWireMessage(encoded), "decode handshake_request"); + expect(decoded.payload.traitId).toBe(0); + expect(decoded.payload.methodId).toBe(0); + expect(toHex(decoded.payload.value)).toBe(toHex(inner)); }); - it("encodes account_get_request (discriminant 22) to match the golden fixture", () => { + it("encodes account_get_request (pair (1, 4)) to match the golden fixture", () => { // payload = V1(("foo", 0u32)); same vector as the Rust golden fixture. const inner = new Uint8Array([ 0x00, // V1 variant @@ -63,12 +81,16 @@ describe("encodeWireMessage / decodeWireMessage wire equality", () => { const encoded = unwrap( encodeWireMessage({ requestId: "p:1", - payload: { id: W.ACCOUNT_GET_ACCOUNT.request, value: inner }, + payload: { + traitId: W.ACCOUNT_GET_ACCOUNT.trait, + methodId: W.ACCOUNT_GET_ACCOUNT.request, + value: inner, + }, }), "encode account_get_request", ); - expect(toHex(encoded)).toBe(toHex(expectedWire(22, inner))); - expect(toHex(encoded)).toBe("0c703a3116000c666f6f00000000"); + expect(toHex(encoded)).toBe(toHex(expectedWire(1, 4, inner))); + expect(toHex(encoded)).toBe("0c703a310104000c666f6f00000000"); }); it("round-trips a local_storage_read frame through encode + decode", () => { @@ -76,30 +98,54 @@ describe("encodeWireMessage / decodeWireMessage wire equality", () => { const encoded = unwrap( encodeWireMessage({ requestId: "p:1", - payload: { id: W.LOCAL_STORAGE_READ.request, value: inner }, + payload: { + traitId: W.LOCAL_STORAGE_READ.trait, + methodId: W.LOCAL_STORAGE_READ.request, + value: inner, + }, }), "encode local_storage_read_request", ); const decoded = unwrap(decodeWireMessage(encoded), "decode local_storage_read_request"); expect(decoded.requestId).toBe("p:1"); - expect(decoded.payload.id).toBe(W.LOCAL_STORAGE_READ.request); + expect(decoded.payload.traitId).toBe(W.LOCAL_STORAGE_READ.trait); + expect(decoded.payload.methodId).toBe(W.LOCAL_STORAGE_READ.request); expect(toHex(decoded.payload.value)).toBe(toHex(inner)); }); - it("rejects an invalid outbound discriminant", () => { + it("rejects an invalid outbound trait discriminant", () => { const result = encodeWireMessage({ requestId: "p:1", - payload: { id: 256, value: new Uint8Array() }, + payload: { traitId: 256, methodId: 0, value: new Uint8Array() }, }); expect(result.isErr()).toBe(true); - expect(result._unsafeUnwrapErr().message).toMatch(/Invalid wire discriminant/); + expect(result._unsafeUnwrapErr().message).toMatch(/Invalid wire trait discriminant/); }); - it("rejects a truncated frame with no discriminant byte", () => { + it("rejects an invalid outbound method discriminant", () => { + const result = encodeWireMessage({ + requestId: "p:1", + payload: { traitId: 0, methodId: 256, value: new Uint8Array() }, + }); + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr().message).toMatch(/Invalid wire method discriminant/); + }); + + it("rejects a truncated frame with no trait byte", () => { const truncated = str.enc("p:1"); // just the requestId, nothing after. const result = decodeWireMessage(truncated); expect(result.isErr()).toBe(true); - expect(result._unsafeUnwrapErr().message).toMatch(/missing discriminant byte/); + expect(result._unsafeUnwrapErr().message).toMatch(/missing trait discriminant byte/); + }); + + it("rejects a truncated frame with a trait byte but no method byte", () => { + const reqId = str.enc("p:1"); + const truncated = new Uint8Array(reqId.length + 1); + truncated.set(reqId, 0); + truncated[reqId.length] = 0x00; + const result = decodeWireMessage(truncated); + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr().message).toMatch(/missing method discriminant byte/); }); it("round-trips a 32 KiB requestId via the mode-2 compact-len prefix", () => { @@ -110,7 +156,11 @@ describe("encodeWireMessage / decodeWireMessage wire equality", () => { const encoded = unwrap( encodeWireMessage({ requestId: longId, - payload: { id: W.ACCOUNT_GET_ACCOUNT.request, value: inner }, + payload: { + traitId: W.ACCOUNT_GET_ACCOUNT.trait, + methodId: W.ACCOUNT_GET_ACCOUNT.request, + value: inner, + }, }), "encode long-id account_get_request", ); @@ -119,7 +169,8 @@ describe("encodeWireMessage / decodeWireMessage wire equality", () => { expect(encoded[0] & 0b11).toBe(0b10); const decoded = unwrap(decodeWireMessage(encoded), "decode long-id account_get_request"); expect(decoded.requestId).toBe(longId); - expect(decoded.payload.id).toBe(W.ACCOUNT_GET_ACCOUNT.request); + expect(decoded.payload.traitId).toBe(W.ACCOUNT_GET_ACCOUNT.trait); + expect(decoded.payload.methodId).toBe(W.ACCOUNT_GET_ACCOUNT.request); expect(toHex(decoded.payload.value)).toBe(toHex(inner)); }); }); diff --git a/js/packages/truapi/src/wire-table.test.ts b/js/packages/truapi/src/wire-table.test.ts index 579d3bd9b..f1561c87c 100644 --- a/js/packages/truapi/src/wire-table.test.ts +++ b/js/packages/truapi/src/wire-table.test.ts @@ -1,8 +1,9 @@ // Programmatic wire-equality loop. // // `wire-equality.test.ts` exercises a handful of hand-picked frames. This file -// iterates every generated numeric frame id and asserts the codec round-trips a -// sentinel payload and produces the expected byte layout for each. +// iterates every generated (trait, method) frame pair and asserts the codec +// round-trips a sentinel payload and produces the expected byte layout for +// each. import type { Result } from "neverthrow"; import { describe, expect, it } from "bun:test"; @@ -17,12 +18,18 @@ function toHex(u: Uint8Array): string { .join(""); } -function expectedWire(reqId: string, tagId: number, valueBytes: Uint8Array): Uint8Array { +function expectedWire( + reqId: string, + traitId: number, + methodId: number, + valueBytes: Uint8Array, +): Uint8Array { const idBytes = str.enc(reqId); - const out = new Uint8Array(idBytes.length + 1 + valueBytes.length); + const out = new Uint8Array(idBytes.length + 2 + valueBytes.length); out.set(idBytes, 0); - out[idBytes.length] = tagId; - out.set(valueBytes, idBytes.length + 1); + out[idBytes.length] = traitId; + out[idBytes.length + 1] = methodId; + out.set(valueBytes, idBytes.length + 2); return out; } @@ -37,7 +44,10 @@ function unwrap(result: Result, message: string): T { } const frames = Object.entries(W as Record>).flatMap( - ([method, ids]) => Object.entries(ids).map(([kind, id]) => ({ method, kind, id })), + ([method, ids]) => { + const { trait: traitId, ...kinds } = ids; + return Object.entries(kinds).map(([kind, id]) => ({ method, kind, traitId, id })); + }, ); describe("generated wire-table round-trip", () => { @@ -45,21 +55,31 @@ describe("generated wire-table round-trip", () => { expect(frames.length).toBeGreaterThan(0); }); - // Per-id sentinel payload so any cross-talk between ids surfaces as a + it("gives every constant a trait discriminant", () => { + for (const [method, ids] of Object.entries(W as Record>)) { + expect(Number.isInteger(ids.trait), `${method} is missing a trait id`).toBe(true); + } + }); + + // Per-pair sentinel payload so any cross-talk between pairs surfaces as a // concrete byte mismatch rather than a silent equality. - it.each(frames)("round-trips $method.$kind (id $id)", ({ id }) => { - const sentinel = new Uint8Array([id, 0xa5, ~id & 0xff, 0x5a]); - const requestId = `r:${id}`; + it.each(frames)("round-trips $method.$kind (pair ($traitId, $id))", ({ traitId, id }) => { + const sentinel = new Uint8Array([traitId, id, 0xa5, ~id & 0xff, 0x5a]); + const requestId = `r:${traitId}:${id}`; const encoded = unwrap( - encodeWireMessage({ requestId, payload: { id, value: sentinel } }), + encodeWireMessage({ + requestId, + payload: { traitId, methodId: id, value: sentinel }, + }), "encode", ); - expect(toHex(encoded)).toBe(toHex(expectedWire(requestId, id, sentinel))); + expect(toHex(encoded)).toBe(toHex(expectedWire(requestId, traitId, id, sentinel))); const decoded = unwrap(decodeWireMessage(encoded), "decode"); expect(decoded.requestId).toBe(requestId); - expect(decoded.payload.id).toBe(id); + expect(decoded.payload.traitId).toBe(traitId); + expect(decoded.payload.methodId).toBe(id); expect(toHex(decoded.payload.value)).toBe(toHex(sentinel)); }); }); diff --git a/rust/crates/truapi-codegen/src/main.rs b/rust/crates/truapi-codegen/src/main.rs index 9fd3e0adb..e8455788d 100644 --- a/rust/crates/truapi-codegen/src/main.rs +++ b/rust/crates/truapi-codegen/src/main.rs @@ -39,7 +39,7 @@ struct Cli { client_version: Option, /// Wire codec version for generated handshake calls. - #[arg(long, default_value_t = 1)] + #[arg(long, default_value_t = 2)] codec_version: u8, /// Output directory for generated playground metadata (optional). diff --git a/rust/crates/truapi-codegen/src/rust.rs b/rust/crates/truapi-codegen/src/rust.rs index c78788298..f3c8fef29 100644 --- a/rust/crates/truapi-codegen/src/rust.rs +++ b/rust/crates/truapi-codegen/src/rust.rs @@ -268,6 +268,7 @@ mod tests { traits: vec![TraitDef { name: "Account".to_string(), module_path: Vec::new(), + wire_trait_id: Some(0), methods: vec![make_subscription_method("connection_status_subscribe", 18)], docs: None, }], @@ -299,12 +300,14 @@ mod tests { TraitDef { name: "StatementStore".to_string(), module_path: Vec::new(), + wire_trait_id: Some(1), methods: vec![make_request_method("submit", 62)], docs: None, }, TraitDef { name: "Preimage".to_string(), module_path: Vec::new(), + wire_trait_id: Some(2), methods: vec![make_request_method("submit", 68)], docs: None, }, @@ -351,12 +354,14 @@ mod tests { TraitDef { name: "Foo".to_string(), module_path: Vec::new(), + wire_trait_id: Some(3), methods: vec![make_request_method("bar_baz", 10)], docs: None, }, TraitDef { name: "FooBar".to_string(), module_path: Vec::new(), + wire_trait_id: Some(4), methods: vec![make_request_method("baz", 12)], docs: None, }, @@ -387,6 +392,7 @@ mod tests { traits: vec![TraitDef { name: "Permissions".to_string(), module_path: Vec::new(), + wire_trait_id: Some(5), methods: vec![make_request_method("request_device_permission", 8)], docs: None, }], @@ -413,6 +419,7 @@ mod tests { traits: vec![TraitDef { name: "Permissions".to_string(), module_path: Vec::new(), + wire_trait_id: Some(5), methods: vec![ make_request_method("alpha", 10), make_request_method("beta", 10), @@ -425,7 +432,98 @@ mod tests { let err = generate_wire_table(&api).expect_err("duplicate ids must error"); let msg = format!("{err}"); assert!( - msg.contains("wire id 10 reused"), + msg.contains("wire id (5, 10) reused"), + "unexpected error message: {msg}", + ); + } + + /// Method ids are scoped per trait: two traits may both use method id 0, + /// and the emitted consts carry each trait's discriminant. + #[test] + fn wire_table_allows_same_method_id_in_different_traits() { + let api = ApiDefinition { + traits: vec![ + TraitDef { + name: "StatementStore".to_string(), + module_path: Vec::new(), + wire_trait_id: Some(13), + methods: vec![make_request_method("submit", 0)], + docs: None, + }, + TraitDef { + name: "Preimage".to_string(), + module_path: Vec::new(), + wire_trait_id: Some(10), + methods: vec![make_request_method("submit", 0)], + docs: None, + }, + ], + public_trait_order: vec!["StatementStore".to_string(), "Preimage".to_string()], + types: vec![], + }; + + let table = generate_wire_table(&api).expect("wire_table"); + assert!( + table.contains("trait_id: 13,"), + "missing trait id 13:\n{table}" + ); + assert!( + table.contains("trait_id: 10,"), + "missing trait id 10:\n{table}" + ); + } + + /// Two traits must not share a wire trait id. + #[test] + fn wire_table_rejects_duplicate_trait_ids() { + let api = ApiDefinition { + traits: vec![ + TraitDef { + name: "StatementStore".to_string(), + module_path: Vec::new(), + wire_trait_id: Some(4), + methods: vec![make_request_method("submit", 0)], + docs: None, + }, + TraitDef { + name: "Preimage".to_string(), + module_path: Vec::new(), + wire_trait_id: Some(4), + methods: vec![make_request_method("submit", 0)], + docs: None, + }, + ], + public_trait_order: vec!["StatementStore".to_string(), "Preimage".to_string()], + types: vec![], + }; + + let err = generate_wire_table(&api).expect_err("duplicate trait ids must error"); + let msg = format!("{err}"); + assert!( + msg.contains("wire trait id 4 reused"), + "unexpected error message: {msg}", + ); + } + + /// A trait missing `#[wire_trait(id = N)]` must fail emission. + #[test] + fn wire_table_missing_trait_id_errors() { + let api = ApiDefinition { + traits: vec![TraitDef { + name: "Permissions".to_string(), + module_path: Vec::new(), + wire_trait_id: None, + methods: vec![make_request_method("request_device_permission", 8)], + docs: None, + }], + public_trait_order: vec!["Permissions".to_string()], + types: vec![], + }; + + let err = generate_wire_table(&api).expect_err("missing trait id must error"); + let msg = format!("{err}"); + assert!( + msg.contains("missing #[wire_trait(id = N)]"), "unexpected error message: {msg}", ); } @@ -471,6 +569,7 @@ mod tests { traits: vec![TraitDef { name: "Permissions".to_string(), module_path: Vec::new(), + wire_trait_id: Some(5), methods: vec![method], docs: None, }], @@ -494,6 +593,7 @@ mod tests { traits: vec![TraitDef { name: "Account".to_string(), module_path: Vec::new(), + wire_trait_id: Some(0), methods: vec![method], docs: None, }], @@ -518,6 +618,7 @@ mod tests { traits: vec![TraitDef { name: "Permissions".to_string(), module_path: Vec::new(), + wire_trait_id: Some(5), methods: vec![method], docs: None, }], @@ -541,6 +642,7 @@ mod tests { traits: vec![TraitDef { name: "Account".to_string(), module_path: Vec::new(), + wire_trait_id: Some(0), methods: vec![method], docs: None, }], @@ -572,6 +674,7 @@ mod tests { traits: vec![TraitDef { name: "Permissions".to_string(), module_path: Vec::new(), + wire_trait_id: Some(5), methods: vec![method], docs: None, }], @@ -605,6 +708,7 @@ mod tests { traits: vec![TraitDef { name: "Permissions".to_string(), module_path: Vec::new(), + wire_trait_id: Some(5), methods: vec![method], docs: None, }], @@ -639,6 +743,7 @@ mod tests { traits: vec![TraitDef { name: "Permissions".to_string(), module_path: Vec::new(), + wire_trait_id: Some(5), methods: vec![method], docs: None, }], @@ -668,6 +773,7 @@ mod tests { traits: vec![TraitDef { name: "Permissions".to_string(), module_path: Vec::new(), + wire_trait_id: Some(5), methods: vec![method], docs: None, }], @@ -707,6 +813,7 @@ mod tests { traits: vec![TraitDef { name: "Account".to_string(), module_path: Vec::new(), + wire_trait_id: Some(0), methods: vec![method], docs: None, }], diff --git a/rust/crates/truapi-codegen/src/rust/wire_table.rs b/rust/crates/truapi-codegen/src/rust/wire_table.rs index 8696b5756..050934a72 100644 --- a/rust/crates/truapi-codegen/src/rust/wire_table.rs +++ b/rust/crates/truapi-codegen/src/rust/wire_table.rs @@ -1,12 +1,14 @@ -//! Emits `wire_table.rs`: the (id, tag) lookup table the server uses to -//! pair incoming wire frames with their request, response, or +//! Emits `wire_table.rs`: the (trait, method) discriminant lookup table the +//! server uses to pair incoming wire frames with their request, response, or //! subscription role. //! -//! Per-method `#[wire(...)]` annotations decide id assignment: +//! A trait-level `#[wire_trait(id = N)]` annotation assigns the trait +//! discriminant; per-method `#[wire(...)]` annotations decide method-id +//! assignment within the trait: //! - request methods reserve `(request_id, response_id)`. //! - subscription methods reserve `(start_id, stop_id, interrupt_id, receive_id)`. //! -//! Missing annotations and collisions both hard-fail codegen. +//! Missing annotations and collisions (per trait) both hard-fail codegen. use std::collections::BTreeMap; use std::fmt::Write; @@ -20,12 +22,14 @@ use super::{const_name, wire_method_name}; #[derive(Debug, Clone, Copy)] struct WireEntry { + trait_id: u8, request_id: u8, response_id: u8, } #[derive(Debug, Clone, Copy)] struct SubEntry { + trait_id: u8, start_id: u8, stop_id: u8, interrupt_id: u8, @@ -41,12 +45,25 @@ enum MethodEntry { /// Emit the contents of `wire_table.rs`. pub fn generate_wire_table(api: &ApiDefinition) -> Result { let mut method_entries: Vec<(String, MethodEntry)> = Vec::new(); - let mut seen: BTreeMap = BTreeMap::new(); + let mut seen: BTreeMap<(u8, u8), String> = BTreeMap::new(); + let mut seen_traits: BTreeMap = BTreeMap::new(); let mut seen_methods: BTreeMap = BTreeMap::new(); for trait_def in &api.traits { + // Method-less traits (e.g. the `TrUApi` umbrella trait) own no wire + // frames and need no trait discriminant. + if trait_def.methods.is_empty() { + continue; + } + let trait_id = trait_wire_id(trait_def)?; + if let Some(existing) = seen_traits.insert(trait_id, trait_def.name.clone()) { + bail!( + "wire trait id {trait_id} reused: `{existing}` and `{}` collide", + trait_def.name + ); + } for method in &trait_def.methods { - let entry = method_entry(trait_def, method)?; + let entry = method_entry(trait_def, trait_id, method)?; let wire_method = wire_method_name(&trait_def.name, &method.name); if let Some(existing) = seen_methods.insert( wire_method.clone(), @@ -64,14 +81,31 @@ pub fn generate_wire_table(api: &ApiDefinition) -> Result { } method_entries.sort_by_key(|(_, entry)| match entry { - MethodEntry::Request(WireEntry { request_id, .. }) => *request_id, - MethodEntry::Subscription(SubEntry { start_id, .. }) => *start_id, + MethodEntry::Request(WireEntry { + trait_id, + request_id, + .. + }) => (*trait_id, *request_id), + MethodEntry::Subscription(SubEntry { + trait_id, start_id, .. + }) => (*trait_id, *start_id), }); render(&method_entries) } -fn method_entry(trait_def: &TraitDef, method: &MethodDef) -> Result { +/// The trait's wire discriminant. Every API trait must carry a +/// `#[wire_trait(id = N)]` annotation. +fn trait_wire_id(trait_def: &TraitDef) -> Result { + trait_def.wire_trait_id.ok_or_else(|| { + anyhow::anyhow!( + "trait `{}` is missing #[wire_trait(id = N)] annotation", + trait_def.name + ) + }) +} + +fn method_entry(trait_def: &TraitDef, trait_id: u8, method: &MethodDef) -> Result { let wire = &method.wire; match method.kind { MethodKind::Request => { @@ -95,6 +129,7 @@ fn method_entry(trait_def: &TraitDef, method: &MethodDef) -> Result })?; let response_id = infer_id(wire.response_id, request_id, 1, &method.name)?; Ok(MethodEntry::Request(WireEntry { + trait_id, request_id, response_id, })) @@ -118,6 +153,7 @@ fn method_entry(trait_def: &TraitDef, method: &MethodDef) -> Result let interrupt_id = infer_id(wire.interrupt_id, start_id, 2, &method.name)?; let receive_id = infer_id(wire.receive_id, start_id, 3, &method.name)?; Ok(MethodEntry::Subscription(SubEntry { + trait_id, start_id, stop_id, interrupt_id, @@ -137,33 +173,35 @@ fn infer_id(explicit: Option, anchor: u8, offset: u8, method_name: &str) -> } fn insert_entry( - seen: &mut BTreeMap, + seen: &mut BTreeMap<(u8, u8), String>, method_name: &str, entry: MethodEntry, ) -> Result<()> { - let pairs: Vec<(u8, String)> = match entry { + let pairs: Vec<(u8, u8, String)> = match entry { MethodEntry::Request(WireEntry { + trait_id, request_id, response_id, }) => vec![ - (request_id, format!("{method_name}_request")), - (response_id, format!("{method_name}_response")), + (trait_id, request_id, format!("{method_name}_request")), + (trait_id, response_id, format!("{method_name}_response")), ], MethodEntry::Subscription(SubEntry { + trait_id, start_id, stop_id, interrupt_id, receive_id, }) => vec![ - (start_id, format!("{method_name}_start")), - (stop_id, format!("{method_name}_stop")), - (interrupt_id, format!("{method_name}_interrupt")), - (receive_id, format!("{method_name}_receive")), + (trait_id, start_id, format!("{method_name}_start")), + (trait_id, stop_id, format!("{method_name}_stop")), + (trait_id, interrupt_id, format!("{method_name}_interrupt")), + (trait_id, receive_id, format!("{method_name}_receive")), ], }; - for (id, tag) in pairs { - if let Some(existing) = seen.insert(id, tag.clone()) { - bail!("wire id {id} reused: `{existing}` and `{tag}` collide"); + for (trait_id, id, tag) in pairs { + if let Some(existing) = seen.insert((trait_id, id), tag.clone()) { + bail!("wire id ({trait_id}, {id}) reused: `{existing}` and `{tag}` collide"); } } Ok(()) @@ -178,31 +216,37 @@ fn render(methods: &[(String, MethodEntry)]) -> Result { //! //! Auto-generated by truapi-codegen. Do not edit. //! - //! Each method reserves either two ids (request/response) or four - //! (start/stop/interrupt/receive). The ids for each method are exposed - //! as a named const (`PREIMAGE_SUBMIT`, ...); [`WIRE_TABLE`] and the - //! generated dispatcher both reference those consts so the numbers live - //! in exactly one place. The table is sorted by request/start id. + //! Every frame carries a `(trait, method)` discriminant pair. Each + //! method reserves either two method ids (request/response) or four + //! (start/stop/interrupt/receive) within its trait. The ids for each + //! method are exposed as a named const (`PREIMAGE_SUBMIT`, ...); + //! [`WIRE_TABLE`] and the generated dispatcher both reference those + //! consts so the numbers live in exactly one place. The table is + //! sorted by (trait id, request/start id). /// Request method wire discriminants. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RequestFrameIds {{ - /// Discriminant for the request frame. + /// Trait discriminant carried by both frames. + pub trait_id: u8, + /// Method discriminant for the request frame. pub request_id: u8, - /// Discriminant for the response frame. + /// Method discriminant for the response frame. pub response_id: u8, }} /// Subscription method wire discriminants. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SubscriptionFrameIds {{ - /// Discriminant for the start frame. + /// Trait discriminant carried by all four frames. + pub trait_id: u8, + /// Method discriminant for the start frame. pub start_id: u8, - /// Discriminant for the stop frame. + /// Method discriminant for the stop frame. pub stop_id: u8, - /// Discriminant for the interrupt frame (server-initiated termination). + /// Method discriminant for the interrupt frame (server-initiated termination). pub interrupt_id: u8, - /// Discriminant for each receive frame (a streamed item). + /// Method discriminant for each receive frame (a streamed item). pub receive_id: u8, }} @@ -230,18 +274,21 @@ fn render(methods: &[(String, MethodEntry)]) -> Result { let konst = const_name(name); let block = match entry { MethodEntry::Request(WireEntry { + trait_id, request_id, response_id, }) => formatdoc! { r#" /// Wire discriminants for `{name}`. pub const {konst}: RequestFrameIds = RequestFrameIds {{ + trait_id: {trait_id}, request_id: {request_id}, response_id: {response_id}, }}; "# }, MethodEntry::Subscription(SubEntry { + trait_id, start_id, stop_id, interrupt_id, @@ -250,6 +297,7 @@ fn render(methods: &[(String, MethodEntry)]) -> Result { r#" /// Wire discriminants for `{name}`. pub const {konst}: SubscriptionFrameIds = SubscriptionFrameIds {{ + trait_id: {trait_id}, start_id: {start_id}, stop_id: {stop_id}, interrupt_id: {interrupt_id}, @@ -266,8 +314,9 @@ fn render(methods: &[(String, MethodEntry)]) -> Result { writedoc!( out, r#" - /// The full wire table. Ordering is part of the wire protocol; - /// only ever append. Removed methods leave their slot empty. + /// The full wire table. Trait ids and per-trait method ordering are + /// part of the wire protocol; only ever append within a trait. + /// Removed methods leave their slot empty. pub const WIRE_TABLE: &[WireEntry] = &[ "# ) diff --git a/rust/crates/truapi-codegen/src/rustdoc.rs b/rust/crates/truapi-codegen/src/rustdoc.rs index 933e23ef6..4d3ffd1e2 100644 --- a/rust/crates/truapi-codegen/src/rustdoc.rs +++ b/rust/crates/truapi-codegen/src/rustdoc.rs @@ -68,6 +68,9 @@ pub struct TraitDef { /// Module path leading to the trait, excluding the trait name itself /// (e.g. `["truapi", "api", "account"]`). pub module_path: Vec, + /// Wire-protocol trait discriminant from the `#[wire_trait(id = N)]` + /// attribute: the first byte of the `(trait, method)` pair on the wire. + pub wire_trait_id: Option, /// Methods declared on the trait, in declaration order. pub methods: Vec, /// Rustdoc comment on the trait, with hidden codegen markers stripped. @@ -629,6 +632,7 @@ fn extract_trait( Ok(TraitDef { name, module_path, + wire_trait_id: item.docs.as_deref().and_then(extract_wire_trait_id), methods, docs: clean_docs(item.docs.as_deref()), }) @@ -778,6 +782,26 @@ fn is_codegen_doc_marker(line: &str) -> bool { line.starts_with("@wire_") } +/// Extracts the `@wire_trait_id=N` marker from a trait's doc comment block. +/// Annotated traits carry the marker via the `#[wire_trait(id = N)]` +/// proc-macro, which appends a hidden doc string so it propagates through +/// rustdoc JSON. +fn extract_wire_trait_id(docs: &str) -> Option { + for line in docs.lines() { + let line = line.trim_start(); + let Some(value) = line.strip_prefix("@wire_trait_id=") else { + continue; + }; + let end = value + .find(|c: char| !c.is_ascii_digit()) + .unwrap_or(value.len()); + if let Ok(id) = value[..end].parse::() { + return Some(id); + } + } + None +} + /// Extracts `@wire__id=N` markers from a doc comment block. Annotated /// methods carry these markers via the `#[wire(...)]` proc-macro, which appends /// hidden doc strings so they propagate through rustdoc JSON. @@ -1450,11 +1474,22 @@ mod tests { #[test] fn clean_docs_strips_wire_markers() { - let docs = "Trait summary.\n\n@wire_request_id=7\n"; + let docs = "Trait summary.\n\n@wire_request_id=7\n@wire_trait_id=3\n"; assert_eq!(clean_docs(Some(docs)).as_deref(), Some("Trait summary.")); } + #[test] + fn extract_wire_trait_id_reads_marker() { + assert_eq!( + extract_wire_trait_id("Trait summary.\n\n@wire_trait_id=14\n"), + Some(14) + ); + assert_eq!(extract_wire_trait_id("Trait summary."), None); + // Out-of-range values are ignored rather than truncated. + assert_eq!(extract_wire_trait_id("@wire_trait_id=300"), None); + } + #[test] fn parse_accepts_tested_format_version() { let json = format!(r#"{{ "format_version": {MIN_FORMAT_VERSION}, "index": {{}} }}"#); diff --git a/rust/crates/truapi-codegen/src/ts.rs b/rust/crates/truapi-codegen/src/ts.rs index 019c33f7f..d0a012555 100644 --- a/rust/crates/truapi-codegen/src/ts.rs +++ b/rust/crates/truapi-codegen/src/ts.rs @@ -583,25 +583,42 @@ fn method_wire_sort_id(method: &MethodDef) -> u8 { fn generate_wire_table(api: &ApiDefinition, target_version: u32) -> Result { let wrappers = collect_versioned_wrappers(api); - let mut seen: BTreeMap = BTreeMap::new(); - let mut constants: Vec<(String, ExpandedWireIds)> = Vec::new(); + let mut seen: BTreeMap<(u8, u8), String> = BTreeMap::new(); + let mut seen_traits: BTreeMap = BTreeMap::new(); + let mut constants: Vec<(String, u8, ExpandedWireIds)> = Vec::new(); for trait_def in &api.traits { + // Method-less traits (e.g. the `TrUApi` umbrella trait) own no wire + // frames and need no trait discriminant. + if trait_def.methods.is_empty() { + continue; + } + let trait_id = trait_wire_id(trait_def)?; + if let Some(existing) = seen_traits.insert(trait_id, trait_def.name.clone()) { + bail!( + "wire trait id {trait_id} reused: `{existing}` and `{}` collide", + trait_def.name + ); + } for method in &trait_def.methods { if !method_is_included(trait_def, method, &wrappers, target_version)? { continue; } let wire_ids = wire_ids_for_method(trait_def, method)?; for (id, tag) in wire_ids.entries(&method.name) { - if let Some(existing) = seen.insert(id, tag.clone()) { - bail!("wire id {id} reused: `{existing}` and `{tag}` collide"); + if let Some(existing) = seen.insert((trait_id, id), tag.clone()) { + bail!("wire id ({trait_id}, {id}) reused: `{existing}` and `{tag}` collide"); } } - constants.push((wire_const_name(&trait_def.name, &method.name), wire_ids)); + constants.push(( + wire_const_name(&trait_def.name, &method.name), + trait_id, + wire_ids, + )); } } - constants.sort_by_key(|(_, ids)| ids.sort_id()); + constants.sort_by_key(|(_, trait_id, ids)| (*trait_id, ids.sort_id())); let mut out = String::new(); writedoc!( @@ -611,12 +628,13 @@ fn generate_wire_table(api: &ApiDefinition, target_version: u32) -> Result Result Result Result Result { + trait_def.wire_trait_id.ok_or_else(|| { + anyhow::anyhow!( + "trait `{}` is missing #[wire_trait(id = N)] annotation", + trait_def.name + ) + }) +} + fn method_is_included( trait_def: &TraitDef, method: &MethodDef, @@ -2375,12 +2406,14 @@ mod tests { let json_rpc = TraitDef { name: "JsonRpc".to_string(), module_path: Vec::new(), + wire_trait_id: Some(6), methods: Vec::new(), docs: None, }; let system = TraitDef { name: "System".to_string(), module_path: Vec::new(), + wire_trait_id: Some(7), methods: Vec::new(), docs: None, }; @@ -2419,6 +2452,7 @@ mod tests { traits: vec![TraitDef { name: "Example".to_string(), module_path: Vec::new(), + wire_trait_id: Some(8), methods, docs: None, }], @@ -2647,6 +2681,7 @@ mod tests { .expect("generate wire table"); assert!(source.contains("export const EXAMPLE_STREAM = {")); + assert!(source.contains(" trait: 8,")); assert!(source.contains(" start: 2,")); assert!(source.contains(" receive: 5,")); assert!(source.contains("export const EXAMPLE_LATER = {")); @@ -2672,7 +2707,7 @@ mod tests { ) .expect_err("duplicate ids must error"); - assert!(err.to_string().contains("wire id 3 reused")); + assert!(err.to_string().contains("wire id (8, 3) reused")); } #[test] @@ -2730,12 +2765,92 @@ mod tests { assert!(err.to_string().contains("wire id overflow")); } + /// Method ids are scoped per trait: two traits may both use method id 0. + #[test] + fn generate_wire_table_allows_same_method_id_in_different_traits() { + let api = ApiDefinition { + traits: vec![ + TraitDef { + name: "Alpha".to_string(), + module_path: Vec::new(), + wire_trait_id: Some(0), + methods: vec![request_method("first", Some(0))], + docs: None, + }, + TraitDef { + name: "Beta".to_string(), + module_path: Vec::new(), + wire_trait_id: Some(1), + methods: vec![request_method("second", Some(0))], + docs: None, + }, + ], + public_trait_order: Vec::new(), + types: Vec::new(), + }; + + let source = generate_wire_table(&api, 2).expect("generate wire table"); + + assert!(source.contains("export const ALPHA_FIRST = {")); + assert!(source.contains("export const BETA_SECOND = {")); + assert!(source.contains(" trait: 0,")); + assert!(source.contains(" trait: 1,")); + } + + /// Two traits must not share a wire trait id. + #[test] + fn generate_wire_table_rejects_duplicate_trait_ids() { + let api = ApiDefinition { + traits: vec![ + TraitDef { + name: "Alpha".to_string(), + module_path: Vec::new(), + wire_trait_id: Some(3), + methods: vec![request_method("first", Some(0))], + docs: None, + }, + TraitDef { + name: "Beta".to_string(), + module_path: Vec::new(), + wire_trait_id: Some(3), + methods: vec![request_method("second", Some(0))], + docs: None, + }, + ], + public_trait_order: Vec::new(), + types: Vec::new(), + }; + + let err = generate_wire_table(&api, 2).expect_err("duplicate trait ids must error"); + assert!(err.to_string().contains("wire trait id 3 reused")); + } + + /// A trait without `#[wire_trait(id = N)]` must fail emission. + #[test] + fn generate_wire_table_rejects_missing_trait_id() { + let api = ApiDefinition { + traits: vec![TraitDef { + name: "Alpha".to_string(), + module_path: Vec::new(), + wire_trait_id: None, + methods: vec![request_method("first", Some(0))], + docs: None, + }], + public_trait_order: Vec::new(), + types: Vec::new(), + }; + + let err = generate_wire_table(&api, 2).expect_err("missing trait id must error"); + assert!(err.to_string().contains("missing #[wire_trait(id = N)]")); + } + #[test] fn generate_wire_table_filters_methods_by_target_version() { let api = ApiDefinition { traits: vec![TraitDef { name: "Example".to_string(), module_path: Vec::new(), + wire_trait_id: Some(8), methods: vec![ request_method_with_wrappers( "legacy", @@ -2782,6 +2897,7 @@ mod tests { TraitDef { name: "Legacy".to_string(), module_path: Vec::new(), + wire_trait_id: Some(9), methods: vec![request_method_with_wrappers( "legacy_call", Some(2), @@ -2794,6 +2910,7 @@ mod tests { TraitDef { name: "FutureOnly".to_string(), module_path: Vec::new(), + wire_trait_id: Some(10), methods: vec![request_method_with_wrappers( "future_call", Some(4), @@ -2831,6 +2948,7 @@ mod tests { traits: vec![TraitDef { name: "Example".to_string(), module_path: Vec::new(), + wire_trait_id: Some(8), methods: vec![MethodDef { name: "example_call".to_string(), kind: MethodKind::Request, @@ -2880,6 +2998,7 @@ mod tests { traits: vec![TraitDef { name: "Example".to_string(), module_path: Vec::new(), + wire_trait_id: Some(8), methods: vec![ MethodDef { name: "legacy_call".to_string(), @@ -2947,6 +3066,7 @@ mod tests { traits: vec![TraitDef { name: "Example".to_string(), module_path: Vec::new(), + wire_trait_id: Some(8), methods: vec![MethodDef { name: "example_call".to_string(), kind: MethodKind::Request, @@ -2993,6 +3113,7 @@ mod tests { traits: vec![TraitDef { name: "Example".to_string(), module_path: Vec::new(), + wire_trait_id: Some(8), methods: vec![MethodDef { name: "example_call".to_string(), kind: MethodKind::Request, diff --git a/rust/crates/truapi-codegen/tests/golden/wire_table.rs b/rust/crates/truapi-codegen/tests/golden/wire_table.rs index 7360d0427..ecb2619fa 100644 --- a/rust/crates/truapi-codegen/tests/golden/wire_table.rs +++ b/rust/crates/truapi-codegen/tests/golden/wire_table.rs @@ -2,31 +2,37 @@ //! //! Auto-generated by truapi-codegen. Do not edit. //! -//! Each method reserves either two ids (request/response) or four -//! (start/stop/interrupt/receive). The ids for each method are exposed -//! as a named const (`PREIMAGE_SUBMIT`, ...); [`WIRE_TABLE`] and the -//! generated dispatcher both reference those consts so the numbers live -//! in exactly one place. The table is sorted by request/start id. +//! Every frame carries a `(trait, method)` discriminant pair. Each +//! method reserves either two method ids (request/response) or four +//! (start/stop/interrupt/receive) within its trait. The ids for each +//! method are exposed as a named const (`PREIMAGE_SUBMIT`, ...); +//! [`WIRE_TABLE`] and the generated dispatcher both reference those +//! consts so the numbers live in exactly one place. The table is +//! sorted by (trait id, request/start id). /// Request method wire discriminants. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RequestFrameIds { - /// Discriminant for the request frame. + /// Trait discriminant carried by both frames. + pub trait_id: u8, + /// Method discriminant for the request frame. pub request_id: u8, - /// Discriminant for the response frame. + /// Method discriminant for the response frame. pub response_id: u8, } /// Subscription method wire discriminants. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SubscriptionFrameIds { - /// Discriminant for the start frame. + /// Trait discriminant carried by all four frames. + pub trait_id: u8, + /// Method discriminant for the start frame. pub start_id: u8, - /// Discriminant for the stop frame. + /// Method discriminant for the stop frame. pub stop_id: u8, - /// Discriminant for the interrupt frame (server-initiated termination). + /// Method discriminant for the interrupt frame (server-initiated termination). pub interrupt_id: u8, - /// Discriminant for each receive frame (a streamed item). + /// Method discriminant for each receive frame (a streamed item). pub receive_id: u8, } @@ -48,426 +54,492 @@ pub enum WireKind { /// Wire discriminants for `system_handshake`. pub const SYSTEM_HANDSHAKE: RequestFrameIds = RequestFrameIds { + trait_id: 0, request_id: 0, response_id: 1, }; /// Wire discriminants for `system_feature_supported`. pub const SYSTEM_FEATURE_SUPPORTED: RequestFrameIds = RequestFrameIds { + trait_id: 0, request_id: 2, response_id: 3, }; -/// Wire discriminants for `notifications_send_push_notification`. -pub const NOTIFICATIONS_SEND_PUSH_NOTIFICATION: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `system_navigate_to`. +pub const SYSTEM_NAVIGATE_TO: RequestFrameIds = RequestFrameIds { + trait_id: 0, request_id: 4, response_id: 5, }; -/// Wire discriminants for `system_navigate_to`. -pub const SYSTEM_NAVIGATE_TO: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_connection_status_subscribe`. +pub const ACCOUNT_CONNECTION_STATUS_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 1, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, +}; + +/// Wire discriminants for `account_get_account`. +pub const ACCOUNT_GET_ACCOUNT: RequestFrameIds = RequestFrameIds { + trait_id: 1, + request_id: 4, + response_id: 5, +}; + +/// Wire discriminants for `account_get_account_alias`. +pub const ACCOUNT_GET_ACCOUNT_ALIAS: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 6, response_id: 7, }; -/// Wire discriminants for `permissions_request_device_permission`. -pub const PERMISSIONS_REQUEST_DEVICE_PERMISSION: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_create_account_proof`. +pub const ACCOUNT_CREATE_ACCOUNT_PROOF: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 8, response_id: 9, }; -/// Wire discriminants for `permissions_request_remote_permission`. -pub const PERMISSIONS_REQUEST_REMOTE_PERMISSION: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_get_legacy_accounts`. +pub const ACCOUNT_GET_LEGACY_ACCOUNTS: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 10, response_id: 11, }; -/// Wire discriminants for `local_storage_read`. -pub const LOCAL_STORAGE_READ: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_get_user_id`. +pub const ACCOUNT_GET_USER_ID: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 12, response_id: 13, }; -/// Wire discriminants for `local_storage_write`. -pub const LOCAL_STORAGE_WRITE: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_request_login`. +pub const ACCOUNT_REQUEST_LOGIN: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 14, response_id: 15, }; -/// Wire discriminants for `local_storage_clear`. -pub const LOCAL_STORAGE_CLEAR: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_sign_vrf`. +pub const ACCOUNT_SIGN_VRF: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 16, response_id: 17, }; -/// Wire discriminants for `account_connection_status_subscribe`. -pub const ACCOUNT_CONNECTION_STATUS_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 18, - stop_id: 19, - interrupt_id: 20, - receive_id: 21, +/// Wire discriminants for `chain_follow_head_subscribe`. +pub const CHAIN_FOLLOW_HEAD_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 2, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, }; -/// Wire discriminants for `account_get_account`. -pub const ACCOUNT_GET_ACCOUNT: RequestFrameIds = RequestFrameIds { - request_id: 22, - response_id: 23, +/// Wire discriminants for `chain_get_head_header`. +pub const CHAIN_GET_HEAD_HEADER: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 4, + response_id: 5, }; -/// Wire discriminants for `account_get_account_alias`. -pub const ACCOUNT_GET_ACCOUNT_ALIAS: RequestFrameIds = RequestFrameIds { - request_id: 24, - response_id: 25, +/// Wire discriminants for `chain_get_head_body`. +pub const CHAIN_GET_HEAD_BODY: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 6, + response_id: 7, }; -/// Wire discriminants for `account_create_account_proof`. -pub const ACCOUNT_CREATE_ACCOUNT_PROOF: RequestFrameIds = RequestFrameIds { - request_id: 26, - response_id: 27, +/// Wire discriminants for `chain_get_head_storage`. +pub const CHAIN_GET_HEAD_STORAGE: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 8, + response_id: 9, }; -/// Wire discriminants for `account_get_legacy_accounts`. -pub const ACCOUNT_GET_LEGACY_ACCOUNTS: RequestFrameIds = RequestFrameIds { - request_id: 28, - response_id: 29, +/// Wire discriminants for `chain_call_head`. +pub const CHAIN_CALL_HEAD: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 10, + response_id: 11, }; -/// Wire discriminants for `signing_create_transaction`. -pub const SIGNING_CREATE_TRANSACTION: RequestFrameIds = RequestFrameIds { - request_id: 30, - response_id: 31, +/// Wire discriminants for `chain_unpin_head`. +pub const CHAIN_UNPIN_HEAD: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 12, + response_id: 13, }; -/// Wire discriminants for `signing_create_transaction_with_legacy_account`. -pub const SIGNING_CREATE_TRANSACTION_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { - request_id: 32, - response_id: 33, +/// Wire discriminants for `chain_continue_head`. +pub const CHAIN_CONTINUE_HEAD: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 14, + response_id: 15, }; -/// Wire discriminants for `signing_sign_raw_with_legacy_account`. -pub const SIGNING_SIGN_RAW_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { - request_id: 34, - response_id: 35, +/// Wire discriminants for `chain_stop_head_operation`. +pub const CHAIN_STOP_HEAD_OPERATION: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 16, + response_id: 17, }; -/// Wire discriminants for `signing_sign_payload_with_legacy_account`. -pub const SIGNING_SIGN_PAYLOAD_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { - request_id: 36, - response_id: 37, +/// Wire discriminants for `chain_get_spec_genesis_hash`. +pub const CHAIN_GET_SPEC_GENESIS_HASH: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 18, + response_id: 19, +}; + +/// Wire discriminants for `chain_get_spec_chain_name`. +pub const CHAIN_GET_SPEC_CHAIN_NAME: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 20, + response_id: 21, +}; + +/// Wire discriminants for `chain_get_spec_properties`. +pub const CHAIN_GET_SPEC_PROPERTIES: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 22, + response_id: 23, +}; + +/// Wire discriminants for `chain_broadcast_transaction`. +pub const CHAIN_BROADCAST_TRANSACTION: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 24, + response_id: 25, +}; + +/// Wire discriminants for `chain_stop_transaction`. +pub const CHAIN_STOP_TRANSACTION: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 26, + response_id: 27, }; /// Wire discriminants for `chat_create_room`. pub const CHAT_CREATE_ROOM: RequestFrameIds = RequestFrameIds { - request_id: 38, - response_id: 39, + trait_id: 3, + request_id: 0, + response_id: 1, }; /// Wire discriminants for `chat_register_bot`. pub const CHAT_REGISTER_BOT: RequestFrameIds = RequestFrameIds { - request_id: 40, - response_id: 41, + trait_id: 3, + request_id: 2, + response_id: 3, }; /// Wire discriminants for `chat_list_subscribe`. pub const CHAT_LIST_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 42, - stop_id: 43, - interrupt_id: 44, - receive_id: 45, + trait_id: 3, + start_id: 4, + stop_id: 5, + interrupt_id: 6, + receive_id: 7, }; /// Wire discriminants for `chat_post_message`. pub const CHAT_POST_MESSAGE: RequestFrameIds = RequestFrameIds { - request_id: 46, - response_id: 47, + trait_id: 3, + request_id: 8, + response_id: 9, }; /// Wire discriminants for `chat_action_subscribe`. pub const CHAT_ACTION_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 48, - stop_id: 49, - interrupt_id: 50, - receive_id: 51, + trait_id: 3, + start_id: 10, + stop_id: 11, + interrupt_id: 12, + receive_id: 13, }; /// Wire discriminants for `chat_custom_message_render_subscribe`. pub const CHAT_CUSTOM_MESSAGE_RENDER_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 52, - stop_id: 53, - interrupt_id: 54, - receive_id: 55, -}; - -/// Wire discriminants for `statement_store_subscribe`. -pub const STATEMENT_STORE_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 56, - stop_id: 57, - interrupt_id: 58, - receive_id: 59, -}; - -/// Wire discriminants for `statement_store_create_proof`. -pub const STATEMENT_STORE_CREATE_PROOF: RequestFrameIds = RequestFrameIds { - request_id: 60, - response_id: 61, -}; - -/// Wire discriminants for `statement_store_submit`. -pub const STATEMENT_STORE_SUBMIT: RequestFrameIds = RequestFrameIds { - request_id: 62, - response_id: 63, -}; - -/// Wire discriminants for `preimage_lookup_subscribe`. -pub const PREIMAGE_LOOKUP_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 64, - stop_id: 65, - interrupt_id: 66, - receive_id: 67, -}; - -/// Wire discriminants for `preimage_submit`. -pub const PREIMAGE_SUBMIT: RequestFrameIds = RequestFrameIds { - request_id: 68, - response_id: 69, -}; - -/// Wire discriminants for `chain_follow_head_subscribe`. -pub const CHAIN_FOLLOW_HEAD_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 76, - stop_id: 77, - interrupt_id: 78, - receive_id: 79, -}; - -/// Wire discriminants for `chain_get_head_header`. -pub const CHAIN_GET_HEAD_HEADER: RequestFrameIds = RequestFrameIds { - request_id: 80, - response_id: 81, -}; - -/// Wire discriminants for `chain_get_head_body`. -pub const CHAIN_GET_HEAD_BODY: RequestFrameIds = RequestFrameIds { - request_id: 82, - response_id: 83, -}; - -/// Wire discriminants for `chain_get_head_storage`. -pub const CHAIN_GET_HEAD_STORAGE: RequestFrameIds = RequestFrameIds { - request_id: 84, - response_id: 85, -}; - -/// Wire discriminants for `chain_call_head`. -pub const CHAIN_CALL_HEAD: RequestFrameIds = RequestFrameIds { - request_id: 86, - response_id: 87, + trait_id: 3, + start_id: 14, + stop_id: 15, + interrupt_id: 16, + receive_id: 17, }; -/// Wire discriminants for `chain_unpin_head`. -pub const CHAIN_UNPIN_HEAD: RequestFrameIds = RequestFrameIds { - request_id: 88, - response_id: 89, +/// Wire discriminants for `coin_payment_create_purse`. +pub const COIN_PAYMENT_CREATE_PURSE: RequestFrameIds = RequestFrameIds { + trait_id: 4, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `chain_continue_head`. -pub const CHAIN_CONTINUE_HEAD: RequestFrameIds = RequestFrameIds { - request_id: 90, - response_id: 91, +/// Wire discriminants for `coin_payment_query_purse`. +pub const COIN_PAYMENT_QUERY_PURSE: RequestFrameIds = RequestFrameIds { + trait_id: 4, + request_id: 2, + response_id: 3, }; -/// Wire discriminants for `chain_stop_head_operation`. -pub const CHAIN_STOP_HEAD_OPERATION: RequestFrameIds = RequestFrameIds { - request_id: 92, - response_id: 93, +/// Wire discriminants for `coin_payment_rebalance_purse`. +pub const COIN_PAYMENT_REBALANCE_PURSE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 4, + start_id: 4, + stop_id: 5, + interrupt_id: 6, + receive_id: 7, }; -/// Wire discriminants for `chain_get_spec_genesis_hash`. -pub const CHAIN_GET_SPEC_GENESIS_HASH: RequestFrameIds = RequestFrameIds { - request_id: 94, - response_id: 95, +/// Wire discriminants for `coin_payment_delete_purse`. +pub const COIN_PAYMENT_DELETE_PURSE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 4, + start_id: 8, + stop_id: 9, + interrupt_id: 10, + receive_id: 11, }; -/// Wire discriminants for `chain_get_spec_chain_name`. -pub const CHAIN_GET_SPEC_CHAIN_NAME: RequestFrameIds = RequestFrameIds { - request_id: 96, - response_id: 97, +/// Wire discriminants for `coin_payment_create_receivable`. +pub const COIN_PAYMENT_CREATE_RECEIVABLE: RequestFrameIds = RequestFrameIds { + trait_id: 4, + request_id: 12, + response_id: 13, }; -/// Wire discriminants for `chain_get_spec_properties`. -pub const CHAIN_GET_SPEC_PROPERTIES: RequestFrameIds = RequestFrameIds { - request_id: 98, - response_id: 99, +/// Wire discriminants for `coin_payment_create_cheque`. +pub const COIN_PAYMENT_CREATE_CHEQUE: RequestFrameIds = RequestFrameIds { + trait_id: 4, + request_id: 14, + response_id: 15, }; -/// Wire discriminants for `chain_broadcast_transaction`. -pub const CHAIN_BROADCAST_TRANSACTION: RequestFrameIds = RequestFrameIds { - request_id: 100, - response_id: 101, +/// Wire discriminants for `coin_payment_deposit`. +pub const COIN_PAYMENT_DEPOSIT: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 4, + start_id: 16, + stop_id: 17, + interrupt_id: 18, + receive_id: 19, }; -/// Wire discriminants for `chain_stop_transaction`. -pub const CHAIN_STOP_TRANSACTION: RequestFrameIds = RequestFrameIds { - request_id: 102, - response_id: 103, +/// Wire discriminants for `coin_payment_refund`. +pub const COIN_PAYMENT_REFUND: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 4, + start_id: 20, + stop_id: 21, + interrupt_id: 22, + receive_id: 23, }; -/// Wire discriminants for `theme_subscribe`. -pub const THEME_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 104, - stop_id: 105, - interrupt_id: 106, - receive_id: 107, +/// Wire discriminants for `coin_payment_listen_for_payment`. +pub const COIN_PAYMENT_LISTEN_FOR_PAYMENT: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 4, + start_id: 24, + stop_id: 25, + interrupt_id: 26, + receive_id: 27, }; /// Wire discriminants for `entropy_derive`. pub const ENTROPY_DERIVE: RequestFrameIds = RequestFrameIds { - request_id: 108, - response_id: 109, + trait_id: 5, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `account_get_user_id`. -pub const ACCOUNT_GET_USER_ID: RequestFrameIds = RequestFrameIds { - request_id: 110, - response_id: 111, +/// Wire discriminants for `local_storage_read`. +pub const LOCAL_STORAGE_READ: RequestFrameIds = RequestFrameIds { + trait_id: 6, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `account_request_login`. -pub const ACCOUNT_REQUEST_LOGIN: RequestFrameIds = RequestFrameIds { - request_id: 112, - response_id: 113, +/// Wire discriminants for `local_storage_write`. +pub const LOCAL_STORAGE_WRITE: RequestFrameIds = RequestFrameIds { + trait_id: 6, + request_id: 2, + response_id: 3, }; -/// Wire discriminants for `signing_sign_raw`. -pub const SIGNING_SIGN_RAW: RequestFrameIds = RequestFrameIds { - request_id: 114, - response_id: 115, +/// Wire discriminants for `local_storage_clear`. +pub const LOCAL_STORAGE_CLEAR: RequestFrameIds = RequestFrameIds { + trait_id: 6, + request_id: 4, + response_id: 5, }; -/// Wire discriminants for `signing_sign_payload`. -pub const SIGNING_SIGN_PAYLOAD: RequestFrameIds = RequestFrameIds { - request_id: 116, - response_id: 117, +/// Wire discriminants for `notifications_send_push_notification`. +pub const NOTIFICATIONS_SEND_PUSH_NOTIFICATION: RequestFrameIds = RequestFrameIds { + trait_id: 7, + request_id: 0, + response_id: 1, +}; + +/// Wire discriminants for `notifications_cancel_push_notification`. +pub const NOTIFICATIONS_CANCEL_PUSH_NOTIFICATION: RequestFrameIds = RequestFrameIds { + trait_id: 7, + request_id: 2, + response_id: 3, }; /// Wire discriminants for `payment_balance_subscribe`. pub const PAYMENT_BALANCE_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 118, - stop_id: 119, - interrupt_id: 120, - receive_id: 121, + trait_id: 8, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, }; /// Wire discriminants for `payment_top_up`. pub const PAYMENT_TOP_UP: RequestFrameIds = RequestFrameIds { - request_id: 122, - response_id: 123, + trait_id: 8, + request_id: 4, + response_id: 5, }; /// Wire discriminants for `payment_request`. pub const PAYMENT_REQUEST: RequestFrameIds = RequestFrameIds { - request_id: 124, - response_id: 125, + trait_id: 8, + request_id: 6, + response_id: 7, }; /// Wire discriminants for `payment_status_subscribe`. pub const PAYMENT_STATUS_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 126, - stop_id: 127, - interrupt_id: 128, - receive_id: 129, + trait_id: 8, + start_id: 8, + stop_id: 9, + interrupt_id: 10, + receive_id: 11, }; -/// Wire discriminants for `resource_allocation_request`. -pub const RESOURCE_ALLOCATION_REQUEST: RequestFrameIds = RequestFrameIds { - request_id: 130, - response_id: 131, +/// Wire discriminants for `permissions_request_device_permission`. +pub const PERMISSIONS_REQUEST_DEVICE_PERMISSION: RequestFrameIds = RequestFrameIds { + trait_id: 9, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `statement_store_create_proof_authorized`. -pub const STATEMENT_STORE_CREATE_PROOF_AUTHORIZED: RequestFrameIds = RequestFrameIds { - request_id: 132, - response_id: 133, +/// Wire discriminants for `permissions_request_remote_permission`. +pub const PERMISSIONS_REQUEST_REMOTE_PERMISSION: RequestFrameIds = RequestFrameIds { + trait_id: 9, + request_id: 2, + response_id: 3, }; -/// Wire discriminants for `notifications_cancel_push_notification`. -pub const NOTIFICATIONS_CANCEL_PUSH_NOTIFICATION: RequestFrameIds = RequestFrameIds { - request_id: 134, - response_id: 135, +/// Wire discriminants for `preimage_lookup_subscribe`. +pub const PREIMAGE_LOOKUP_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 10, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, }; -/// Wire discriminants for `coin_payment_create_purse`. -pub const COIN_PAYMENT_CREATE_PURSE: RequestFrameIds = RequestFrameIds { - request_id: 136, - response_id: 137, +/// Wire discriminants for `preimage_submit`. +pub const PREIMAGE_SUBMIT: RequestFrameIds = RequestFrameIds { + trait_id: 10, + request_id: 4, + response_id: 5, }; -/// Wire discriminants for `coin_payment_query_purse`. -pub const COIN_PAYMENT_QUERY_PURSE: RequestFrameIds = RequestFrameIds { - request_id: 138, - response_id: 139, +/// Wire discriminants for `resource_allocation_request`. +pub const RESOURCE_ALLOCATION_REQUEST: RequestFrameIds = RequestFrameIds { + trait_id: 11, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `coin_payment_rebalance_purse`. -pub const COIN_PAYMENT_REBALANCE_PURSE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 140, - stop_id: 141, - interrupt_id: 142, - receive_id: 143, +/// Wire discriminants for `signing_create_transaction`. +pub const SIGNING_CREATE_TRANSACTION: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `coin_payment_delete_purse`. -pub const COIN_PAYMENT_DELETE_PURSE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 144, - stop_id: 145, - interrupt_id: 146, - receive_id: 147, +/// Wire discriminants for `signing_create_transaction_with_legacy_account`. +pub const SIGNING_CREATE_TRANSACTION_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 2, + response_id: 3, }; -/// Wire discriminants for `coin_payment_create_receivable`. -pub const COIN_PAYMENT_CREATE_RECEIVABLE: RequestFrameIds = RequestFrameIds { - request_id: 148, - response_id: 149, +/// Wire discriminants for `signing_sign_raw_with_legacy_account`. +pub const SIGNING_SIGN_RAW_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 4, + response_id: 5, +}; + +/// Wire discriminants for `signing_sign_payload_with_legacy_account`. +pub const SIGNING_SIGN_PAYLOAD_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 6, + response_id: 7, +}; + +/// Wire discriminants for `signing_sign_raw`. +pub const SIGNING_SIGN_RAW: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 8, + response_id: 9, +}; + +/// Wire discriminants for `signing_sign_payload`. +pub const SIGNING_SIGN_PAYLOAD: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 10, + response_id: 11, }; -/// Wire discriminants for `coin_payment_create_cheque`. -pub const COIN_PAYMENT_CREATE_CHEQUE: RequestFrameIds = RequestFrameIds { - request_id: 150, - response_id: 151, +/// Wire discriminants for `statement_store_subscribe`. +pub const STATEMENT_STORE_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 13, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, }; -/// Wire discriminants for `coin_payment_deposit`. -pub const COIN_PAYMENT_DEPOSIT: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 152, - stop_id: 153, - interrupt_id: 154, - receive_id: 155, +/// Wire discriminants for `statement_store_create_proof`. +pub const STATEMENT_STORE_CREATE_PROOF: RequestFrameIds = RequestFrameIds { + trait_id: 13, + request_id: 4, + response_id: 5, }; -/// Wire discriminants for `coin_payment_refund`. -pub const COIN_PAYMENT_REFUND: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 156, - stop_id: 157, - interrupt_id: 158, - receive_id: 159, +/// Wire discriminants for `statement_store_submit`. +pub const STATEMENT_STORE_SUBMIT: RequestFrameIds = RequestFrameIds { + trait_id: 13, + request_id: 6, + response_id: 7, }; -/// Wire discriminants for `coin_payment_listen_for_payment`. -pub const COIN_PAYMENT_LISTEN_FOR_PAYMENT: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 160, - stop_id: 161, - interrupt_id: 162, - receive_id: 163, +/// Wire discriminants for `statement_store_create_proof_authorized`. +pub const STATEMENT_STORE_CREATE_PROOF_AUTHORIZED: RequestFrameIds = RequestFrameIds { + trait_id: 13, + request_id: 8, + response_id: 9, }; -/// Wire discriminants for `account_sign_vrf`. -pub const ACCOUNT_SIGN_VRF: RequestFrameIds = RequestFrameIds { - request_id: 164, - response_id: 165, +/// Wire discriminants for `theme_subscribe`. +pub const THEME_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 14, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, }; -/// The full wire table. Ordering is part of the wire protocol; -/// only ever append. Removed methods leave their slot empty. +/// The full wire table. Trait ids and per-trait method ordering are +/// part of the wire protocol; only ever append within a trait. +/// Removed methods leave their slot empty. pub const WIRE_TABLE: &[WireEntry] = &[ WireEntry { method: "system_handshake", @@ -477,34 +549,10 @@ pub const WIRE_TABLE: &[WireEntry] = &[ method: "system_feature_supported", kind: WireKind::Request(SYSTEM_FEATURE_SUPPORTED), }, - WireEntry { - method: "notifications_send_push_notification", - kind: WireKind::Request(NOTIFICATIONS_SEND_PUSH_NOTIFICATION), - }, WireEntry { method: "system_navigate_to", kind: WireKind::Request(SYSTEM_NAVIGATE_TO), }, - WireEntry { - method: "permissions_request_device_permission", - kind: WireKind::Request(PERMISSIONS_REQUEST_DEVICE_PERMISSION), - }, - WireEntry { - method: "permissions_request_remote_permission", - kind: WireKind::Request(PERMISSIONS_REQUEST_REMOTE_PERMISSION), - }, - WireEntry { - method: "local_storage_read", - kind: WireKind::Request(LOCAL_STORAGE_READ), - }, - WireEntry { - method: "local_storage_write", - kind: WireKind::Request(LOCAL_STORAGE_WRITE), - }, - WireEntry { - method: "local_storage_clear", - kind: WireKind::Request(LOCAL_STORAGE_CLEAR), - }, WireEntry { method: "account_connection_status_subscribe", kind: WireKind::Subscription(ACCOUNT_CONNECTION_STATUS_SUBSCRIBE), @@ -526,64 +574,16 @@ pub const WIRE_TABLE: &[WireEntry] = &[ kind: WireKind::Request(ACCOUNT_GET_LEGACY_ACCOUNTS), }, WireEntry { - method: "signing_create_transaction", - kind: WireKind::Request(SIGNING_CREATE_TRANSACTION), - }, - WireEntry { - method: "signing_create_transaction_with_legacy_account", - kind: WireKind::Request(SIGNING_CREATE_TRANSACTION_WITH_LEGACY_ACCOUNT), - }, - WireEntry { - method: "signing_sign_raw_with_legacy_account", - kind: WireKind::Request(SIGNING_SIGN_RAW_WITH_LEGACY_ACCOUNT), - }, - WireEntry { - method: "signing_sign_payload_with_legacy_account", - kind: WireKind::Request(SIGNING_SIGN_PAYLOAD_WITH_LEGACY_ACCOUNT), - }, - WireEntry { - method: "chat_create_room", - kind: WireKind::Request(CHAT_CREATE_ROOM), - }, - WireEntry { - method: "chat_register_bot", - kind: WireKind::Request(CHAT_REGISTER_BOT), - }, - WireEntry { - method: "chat_list_subscribe", - kind: WireKind::Subscription(CHAT_LIST_SUBSCRIBE), - }, - WireEntry { - method: "chat_post_message", - kind: WireKind::Request(CHAT_POST_MESSAGE), - }, - WireEntry { - method: "chat_action_subscribe", - kind: WireKind::Subscription(CHAT_ACTION_SUBSCRIBE), - }, - WireEntry { - method: "chat_custom_message_render_subscribe", - kind: WireKind::Subscription(CHAT_CUSTOM_MESSAGE_RENDER_SUBSCRIBE), - }, - WireEntry { - method: "statement_store_subscribe", - kind: WireKind::Subscription(STATEMENT_STORE_SUBSCRIBE), - }, - WireEntry { - method: "statement_store_create_proof", - kind: WireKind::Request(STATEMENT_STORE_CREATE_PROOF), - }, - WireEntry { - method: "statement_store_submit", - kind: WireKind::Request(STATEMENT_STORE_SUBMIT), + method: "account_get_user_id", + kind: WireKind::Request(ACCOUNT_GET_USER_ID), }, WireEntry { - method: "preimage_lookup_subscribe", - kind: WireKind::Subscription(PREIMAGE_LOOKUP_SUBSCRIBE), + method: "account_request_login", + kind: WireKind::Request(ACCOUNT_REQUEST_LOGIN), }, WireEntry { - method: "preimage_submit", - kind: WireKind::Request(PREIMAGE_SUBMIT), + method: "account_sign_vrf", + kind: WireKind::Request(ACCOUNT_SIGN_VRF), }, WireEntry { method: "chain_follow_head_subscribe", @@ -638,28 +638,88 @@ pub const WIRE_TABLE: &[WireEntry] = &[ kind: WireKind::Request(CHAIN_STOP_TRANSACTION), }, WireEntry { - method: "theme_subscribe", - kind: WireKind::Subscription(THEME_SUBSCRIBE), + method: "chat_create_room", + kind: WireKind::Request(CHAT_CREATE_ROOM), + }, + WireEntry { + method: "chat_register_bot", + kind: WireKind::Request(CHAT_REGISTER_BOT), + }, + WireEntry { + method: "chat_list_subscribe", + kind: WireKind::Subscription(CHAT_LIST_SUBSCRIBE), + }, + WireEntry { + method: "chat_post_message", + kind: WireKind::Request(CHAT_POST_MESSAGE), + }, + WireEntry { + method: "chat_action_subscribe", + kind: WireKind::Subscription(CHAT_ACTION_SUBSCRIBE), + }, + WireEntry { + method: "chat_custom_message_render_subscribe", + kind: WireKind::Subscription(CHAT_CUSTOM_MESSAGE_RENDER_SUBSCRIBE), + }, + WireEntry { + method: "coin_payment_create_purse", + kind: WireKind::Request(COIN_PAYMENT_CREATE_PURSE), + }, + WireEntry { + method: "coin_payment_query_purse", + kind: WireKind::Request(COIN_PAYMENT_QUERY_PURSE), + }, + WireEntry { + method: "coin_payment_rebalance_purse", + kind: WireKind::Subscription(COIN_PAYMENT_REBALANCE_PURSE), + }, + WireEntry { + method: "coin_payment_delete_purse", + kind: WireKind::Subscription(COIN_PAYMENT_DELETE_PURSE), + }, + WireEntry { + method: "coin_payment_create_receivable", + kind: WireKind::Request(COIN_PAYMENT_CREATE_RECEIVABLE), + }, + WireEntry { + method: "coin_payment_create_cheque", + kind: WireKind::Request(COIN_PAYMENT_CREATE_CHEQUE), + }, + WireEntry { + method: "coin_payment_deposit", + kind: WireKind::Subscription(COIN_PAYMENT_DEPOSIT), + }, + WireEntry { + method: "coin_payment_refund", + kind: WireKind::Subscription(COIN_PAYMENT_REFUND), + }, + WireEntry { + method: "coin_payment_listen_for_payment", + kind: WireKind::Subscription(COIN_PAYMENT_LISTEN_FOR_PAYMENT), }, WireEntry { method: "entropy_derive", kind: WireKind::Request(ENTROPY_DERIVE), }, WireEntry { - method: "account_get_user_id", - kind: WireKind::Request(ACCOUNT_GET_USER_ID), + method: "local_storage_read", + kind: WireKind::Request(LOCAL_STORAGE_READ), }, WireEntry { - method: "account_request_login", - kind: WireKind::Request(ACCOUNT_REQUEST_LOGIN), + method: "local_storage_write", + kind: WireKind::Request(LOCAL_STORAGE_WRITE), }, WireEntry { - method: "signing_sign_raw", - kind: WireKind::Request(SIGNING_SIGN_RAW), + method: "local_storage_clear", + kind: WireKind::Request(LOCAL_STORAGE_CLEAR), }, WireEntry { - method: "signing_sign_payload", - kind: WireKind::Request(SIGNING_SIGN_PAYLOAD), + method: "notifications_send_push_notification", + kind: WireKind::Request(NOTIFICATIONS_SEND_PUSH_NOTIFICATION), + }, + WireEntry { + method: "notifications_cancel_push_notification", + kind: WireKind::Request(NOTIFICATIONS_CANCEL_PUSH_NOTIFICATION), }, WireEntry { method: "payment_balance_subscribe", @@ -678,55 +738,67 @@ pub const WIRE_TABLE: &[WireEntry] = &[ kind: WireKind::Subscription(PAYMENT_STATUS_SUBSCRIBE), }, WireEntry { - method: "resource_allocation_request", - kind: WireKind::Request(RESOURCE_ALLOCATION_REQUEST), + method: "permissions_request_device_permission", + kind: WireKind::Request(PERMISSIONS_REQUEST_DEVICE_PERMISSION), }, WireEntry { - method: "statement_store_create_proof_authorized", - kind: WireKind::Request(STATEMENT_STORE_CREATE_PROOF_AUTHORIZED), + method: "permissions_request_remote_permission", + kind: WireKind::Request(PERMISSIONS_REQUEST_REMOTE_PERMISSION), }, WireEntry { - method: "notifications_cancel_push_notification", - kind: WireKind::Request(NOTIFICATIONS_CANCEL_PUSH_NOTIFICATION), + method: "preimage_lookup_subscribe", + kind: WireKind::Subscription(PREIMAGE_LOOKUP_SUBSCRIBE), }, WireEntry { - method: "coin_payment_create_purse", - kind: WireKind::Request(COIN_PAYMENT_CREATE_PURSE), + method: "preimage_submit", + kind: WireKind::Request(PREIMAGE_SUBMIT), }, WireEntry { - method: "coin_payment_query_purse", - kind: WireKind::Request(COIN_PAYMENT_QUERY_PURSE), + method: "resource_allocation_request", + kind: WireKind::Request(RESOURCE_ALLOCATION_REQUEST), }, WireEntry { - method: "coin_payment_rebalance_purse", - kind: WireKind::Subscription(COIN_PAYMENT_REBALANCE_PURSE), + method: "signing_create_transaction", + kind: WireKind::Request(SIGNING_CREATE_TRANSACTION), }, WireEntry { - method: "coin_payment_delete_purse", - kind: WireKind::Subscription(COIN_PAYMENT_DELETE_PURSE), + method: "signing_create_transaction_with_legacy_account", + kind: WireKind::Request(SIGNING_CREATE_TRANSACTION_WITH_LEGACY_ACCOUNT), }, WireEntry { - method: "coin_payment_create_receivable", - kind: WireKind::Request(COIN_PAYMENT_CREATE_RECEIVABLE), + method: "signing_sign_raw_with_legacy_account", + kind: WireKind::Request(SIGNING_SIGN_RAW_WITH_LEGACY_ACCOUNT), }, WireEntry { - method: "coin_payment_create_cheque", - kind: WireKind::Request(COIN_PAYMENT_CREATE_CHEQUE), + method: "signing_sign_payload_with_legacy_account", + kind: WireKind::Request(SIGNING_SIGN_PAYLOAD_WITH_LEGACY_ACCOUNT), }, WireEntry { - method: "coin_payment_deposit", - kind: WireKind::Subscription(COIN_PAYMENT_DEPOSIT), + method: "signing_sign_raw", + kind: WireKind::Request(SIGNING_SIGN_RAW), }, WireEntry { - method: "coin_payment_refund", - kind: WireKind::Subscription(COIN_PAYMENT_REFUND), + method: "signing_sign_payload", + kind: WireKind::Request(SIGNING_SIGN_PAYLOAD), }, WireEntry { - method: "coin_payment_listen_for_payment", - kind: WireKind::Subscription(COIN_PAYMENT_LISTEN_FOR_PAYMENT), + method: "statement_store_subscribe", + kind: WireKind::Subscription(STATEMENT_STORE_SUBSCRIBE), }, WireEntry { - method: "account_sign_vrf", - kind: WireKind::Request(ACCOUNT_SIGN_VRF), + method: "statement_store_create_proof", + kind: WireKind::Request(STATEMENT_STORE_CREATE_PROOF), + }, + WireEntry { + method: "statement_store_submit", + kind: WireKind::Request(STATEMENT_STORE_SUBMIT), + }, + WireEntry { + method: "statement_store_create_proof_authorized", + kind: WireKind::Request(STATEMENT_STORE_CREATE_PROOF_AUTHORIZED), + }, + WireEntry { + method: "theme_subscribe", + kind: WireKind::Subscription(THEME_SUBSCRIBE), }, ]; diff --git a/rust/crates/truapi-macros/src/lib.rs b/rust/crates/truapi-macros/src/lib.rs index bc7d0dd3f..8d4730a29 100644 --- a/rust/crates/truapi-macros/src/lib.rs +++ b/rust/crates/truapi-macros/src/lib.rs @@ -4,10 +4,12 @@ //! envelopes: the `Vn` enums (with SCALE codec indices) plus their //! `Versioned`/`IntoLatest`/`FromLatest` impls from `truapi::versioned`. //! -//! The `wire` attribute marks a trait method with -//! its wire-protocol discriminant ids. The ids appear on the wire as the u8 discriminant in the -//! `Struct { request_id: str, payload: Enum() }` envelope; method -//! ordering becomes part of the wire protocol. +//! The `wire` attribute marks a trait method with its wire-protocol +//! discriminant ids, and the `wire_trait` attribute marks an API trait with +//! its trait discriminant. Together they form the two-byte +//! `(trait, method)` discriminant pair in the +//! `Struct { request_id: str, payload: (trait, method, bytes) }` envelope; +//! trait and method ordering become part of the wire protocol. //! //! At compile time the macro validates that every id literal is a `u8`. It emits //! a hidden doc line so the value survives into rustdoc JSON, where @@ -25,7 +27,7 @@ use proc_macro2::Literal; use quote::quote; use syn::parse::{Parse, ParseStream}; use syn::{ - Attribute, Ident, ItemFn, LitInt, Token, TraitItemFn, Type, Visibility, braced, + Attribute, Ident, ItemFn, ItemTrait, LitInt, Token, TraitItemFn, Type, Visibility, braced, parse_macro_input, }; @@ -133,6 +135,59 @@ pub fn wire(args: TokenStream, item: TokenStream) -> TokenStream { .into() } +/// Arguments to `#[wire_trait(id = N)]`. +struct WireTraitArgs { + id: u8, +} + +impl Parse for WireTraitArgs { + fn parse(input: ParseStream<'_>) -> syn::Result { + let key: Ident = input.parse()?; + if key != "id" { + return Err(syn::Error::new(key.span(), "expected `id = N`")); + } + input.parse::()?; + let lit: LitInt = input.parse()?; + let id = lit.base10_parse().map_err(|err| { + syn::Error::new(lit.span(), format!("wire trait id must fit in a u8: {err}")) + })?; + if !input.is_empty() { + return Err(input.error("expected a single `id = N` argument")); + } + Ok(Self { id }) + } +} + +/// Mark a TrUAPI service trait with its wire-protocol trait discriminant. +/// +/// ```ignore +/// #[wire_trait(id = 0)] +/// pub trait System: Send + Sync { ... } +/// ``` +/// +/// The trait id is the first byte of the `(trait, method)` discriminant pair +/// every frame of the trait's methods carries on the wire. Expands to the +/// original trait plus a hidden `@wire_trait_id=N` doc tag that +/// `truapi-codegen` extracts from rustdoc JSON. +#[proc_macro_attribute] +pub fn wire_trait(args: TokenStream, item: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as WireTraitArgs); + let tag = format!("@wire_trait_id={}", args.id); + + match syn::parse::(item) { + Ok(mut item_trait) => { + item_trait.attrs.push(syn::parse_quote!(#[doc = #tag])); + quote!(#item_trait).into() + } + Err(_) => syn::Error::new( + proc_macro2::Span::call_site(), + "#[wire_trait] can only be applied to traits", + ) + .to_compile_error() + .into(), + } +} + fn wire_tags(args: &WireArgs) -> Vec { [ ("request_id", args.request_id), diff --git a/rust/crates/truapi-server/src/core.rs b/rust/crates/truapi-server/src/core.rs index cb6df1d52..c42a5ee90 100644 --- a/rust/crates/truapi-server/src/core.rs +++ b/rust/crates/truapi-server/src/core.rs @@ -192,7 +192,8 @@ mod tests { let frame = ProtocolMessage { request_id: "p:1".into(), payload: Payload { - id: ids.request_id, + trait_id: ids.trait_id, + method_id: ids.request_id, value: request.encode(), }, }; @@ -201,7 +202,8 @@ mod tests { .expect("dispatcher should emit a response"); let response = ProtocolMessage::decode(&mut &response_bytes[..]).expect("decode response"); assert_eq!(response.request_id, "p:1"); - assert_eq!(response.payload.id, ids.response_id); + assert_eq!(response.payload.trait_id, ids.trait_id); + assert_eq!(response.payload.method_id, ids.response_id); // Wire payload is `Result`-shaped: // [Ok disc=0x00][V1 variant 0x00][supported=1] assert_eq!(response.payload.value, vec![0x00, 0x00, 0x01]); @@ -216,7 +218,8 @@ mod tests { let frame = ProtocolMessage { request_id: "p:1".into(), payload: Payload { - id: ids.request_id, + trait_id: ids.trait_id, + method_id: ids.request_id, value: request_bytes, }, }; @@ -225,7 +228,8 @@ mod tests { .expect("dispatcher should emit a response"); let response = ProtocolMessage::decode(&mut &response_bytes[..]).expect("decode response"); assert_eq!(response.request_id, "p:1"); - assert_eq!(response.payload.id, ids.response_id); + assert_eq!(response.payload.trait_id, ids.trait_id); + assert_eq!(response.payload.method_id, ids.response_id); response.payload.value } @@ -342,7 +346,8 @@ mod tests { let frame = ProtocolMessage { request_id: "p:1".into(), payload: Payload { - id: sub_ids.start_id, + trait_id: sub_ids.trait_id, + method_id: sub_ids.start_id, value: Vec::new(), }, }; @@ -363,7 +368,8 @@ mod tests { let sent = transport.sent.lock().unwrap().clone(); assert!(!sent.is_empty(), "expected at least one _receive frame"); let first = &sent[0]; - assert_eq!(first.payload.id, sub_ids.receive_id); + assert_eq!(first.payload.trait_id, sub_ids.trait_id); + assert_eq!(first.payload.method_id, sub_ids.receive_id); // V1(Disconnected): V1 variant 0x00, Disconnected discriminant 0x00. assert_eq!(first.payload.value, vec![0x00, 0x00]); } diff --git a/rust/crates/truapi-server/src/dispatcher.rs b/rust/crates/truapi-server/src/dispatcher.rs index 45164c615..f933e1df2 100644 --- a/rust/crates/truapi-server/src/dispatcher.rs +++ b/rust/crates/truapi-server/src/dispatcher.rs @@ -1,10 +1,10 @@ //! Request dispatcher. //! //! Routes incoming frames to the appropriate trait method based on the -//! numeric wire discriminant. The handler set is registered by the -//! auto-generated [`crate::generated::dispatcher::register`] function; this -//! module provides the framework that owns the registration tables and the -//! routing logic. +//! numeric `(trait, method)` wire discriminant pair. The handler set is +//! registered by the auto-generated +//! [`crate::generated::dispatcher::register`] function; this module provides +//! the framework that owns the registration tables and the routing logic. use std::collections::{HashMap, HashSet}; use std::sync::Arc; @@ -48,11 +48,11 @@ pub struct SubscriptionEntry { } /// Routes incoming protocol messages to registered handlers, keyed on the -/// numeric wire discriminant. +/// numeric `(trait, method)` wire discriminant pair. pub struct Dispatcher { - by_request: HashMap, - by_start: HashMap, - stop_ids: HashSet, + by_request: HashMap<(u8, u8), RequestEntry>, + by_start: HashMap<(u8, u8), SubscriptionEntry>, + stop_ids: HashSet<(u8, u8)>, subscriptions: SubscriptionManager, } @@ -67,10 +67,11 @@ impl Dispatcher { } } - /// Register a request-response handler, keyed on `ids.request_id`. Returns - /// the previously registered entry if any; callers (the generated - /// `dispatcher::register`) should treat `Some` as a programming error - /// since each request id must own exactly one handler. + /// Register a request-response handler, keyed on + /// `(ids.trait_id, ids.request_id)`. Returns the previously registered + /// entry if any; callers (the generated `dispatcher::register`) should + /// treat `Some` as a programming error since each discriminant pair must + /// own exactly one handler. pub fn on_request(&mut self, ids: RequestFrameIds, handler: F) -> Option where F: Fn(String, Vec) -> BoxFuture<'static, Result, Vec>> @@ -79,7 +80,7 @@ impl Dispatcher { + 'static, { self.by_request.insert( - ids.request_id, + (ids.trait_id, ids.request_id), RequestEntry { ids, handler: Arc::new(handler), @@ -87,9 +88,10 @@ impl Dispatcher { ) } - /// Register a subscription handler, keyed on `ids.start_id`, and record - /// `ids.stop_id` so a matching `_stop` frame tears the subscription down. - /// Returns the previously registered entry if any. + /// Register a subscription handler, keyed on + /// `(ids.trait_id, ids.start_id)`, and record the stop pair so a matching + /// `_stop` frame tears the subscription down. Returns the previously + /// registered entry if any. pub fn on_subscription( &mut self, ids: SubscriptionFrameIds, @@ -101,9 +103,9 @@ impl Dispatcher { + Sync + 'static, { - self.stop_ids.insert(ids.stop_id); + self.stop_ids.insert((ids.trait_id, ids.stop_id)); self.by_start.insert( - ids.start_id, + (ids.trait_id, ids.start_id), SubscriptionEntry { ids, handler: Arc::new(handler), @@ -112,13 +114,13 @@ impl Dispatcher { } /// Process an incoming protocol message, sending any responses or - /// subscription frames through `transport`. A discriminant with no - /// registered handler is dropped. + /// subscription frames through `transport`. A discriminant pair with no + /// registered handler is reported via an error-level log and dropped. #[instrument(skip_all, fields(runtime.method = "dispatcher.dispatch"))] pub async fn dispatch(&self, message: ProtocolMessage, transport: Arc) { - let id = message.payload.id; + let key = (message.payload.trait_id, message.payload.method_id); - if let Some(entry) = self.by_request.get(&id) { + if let Some(entry) = self.by_request.get(&key) { let request_id = message.request_id.clone(); let value = (entry.handler)(request_id, message.payload.value) .await @@ -126,11 +128,12 @@ impl Dispatcher { transport.send(ProtocolMessage { request_id: message.request_id, payload: Payload { - id: entry.ids.response_id, + trait_id: entry.ids.trait_id, + method_id: entry.ids.response_id, value, }, }); - } else if let Some(entry) = self.by_start.get(&id) { + } else if let Some(entry) = self.by_start.get(&key) { // Reserve the slot before awaiting the handler so a `_stop` // arriving while the handler resolves cancels the pending // subscription instead of racing the registration. @@ -140,6 +143,7 @@ impl Dispatcher { Ok(stream) => { self.subscriptions.activate( token, + entry.ids.trait_id, entry.ids.receive_id, entry.ids.interrupt_id, stream, @@ -151,17 +155,27 @@ impl Dispatcher { transport.send(ProtocolMessage { request_id: message.request_id, payload: Payload { - id: entry.ids.interrupt_id, + trait_id: entry.ids.trait_id, + method_id: entry.ids.interrupt_id, value: err_bytes, }, }); } } - } else if self.stop_ids.contains(&id) { + } else if self.stop_ids.contains(&key) { self.subscriptions.handle_stop(&message.request_id); + } else { + // Response / receive / interrupt frames are handled by the client + // side and never registered here; anything else reaching this arm + // is a wire mismatch that must be visible, not silently dropped. + let (trait_id, method_id) = key; + tracing::error!( + request_id = %message.request_id, + trait_id, + method_id, + "unknown wire discriminant pair ({trait_id}, {method_id}); dropping frame" + ); } - // Unknown discriminant: drop. Response / receive / interrupt frames are - // handled by the client side and never registered here. } /// Cancel every subscription currently owned by this dispatcher. @@ -209,10 +223,14 @@ mod tests { } } - fn make_frame(id: u8, value: Vec) -> ProtocolMessage { + fn make_frame(trait_id: u8, method_id: u8, value: Vec) -> ProtocolMessage { ProtocolMessage { request_id: "p:1".into(), - payload: Payload { id, value }, + payload: Payload { + trait_id, + method_id, + value, + }, } } @@ -224,7 +242,7 @@ mod tests { let dispatcher = Dispatcher::new(test_spawner()); let transport = Arc::new(RecordingTransport::default()); let transport_dyn: Arc = transport.clone(); - let frame = make_frame(250, Vec::new()); + let frame = make_frame(250, 250, Vec::new()); futures::executor::block_on(dispatcher.dispatch(frame, transport_dyn)); assert!( transport.sent().is_empty(), @@ -238,6 +256,7 @@ mod tests { fn dispatch_request_handler_error_emits_response_payload() { let mut dispatcher = Dispatcher::new(test_spawner()); let ids = RequestFrameIds { + trait_id: 7, request_id: 200, response_id: 201, }; @@ -245,11 +264,12 @@ mod tests { Box::pin(async move { Err(vec![9, 8, 7]) }) }); let transport = Arc::new(RecordingTransport::default()); - let frame = make_frame(200, Vec::new()); + let frame = make_frame(7, 200, Vec::new()); futures::executor::block_on(dispatcher.dispatch(frame, transport.clone())); let sent = transport.sent(); assert_eq!(sent.len(), 1, "exactly one response expected"); - assert_eq!(sent[0].payload.id, 201); + assert_eq!(sent[0].payload.trait_id, 7); + assert_eq!(sent[0].payload.method_id, 201); assert_eq!(sent[0].payload.value, vec![9, 8, 7]); } @@ -260,6 +280,7 @@ mod tests { fn register_request_twice_returns_previous_handler() { let mut dispatcher = Dispatcher::new(test_spawner()); let ids = RequestFrameIds { + trait_id: 7, request_id: 200, response_id: 201, }; diff --git a/rust/crates/truapi-server/src/frame.rs b/rust/crates/truapi-server/src/frame.rs index c324200a4..56b35ee5e 100644 --- a/rust/crates/truapi-server/src/frame.rs +++ b/rust/crates/truapi-server/src/frame.rs @@ -4,22 +4,23 @@ //! and a `payload`. On the wire the envelope is: //! //! ```text -//! [requestId: SCALE str][discriminant: u8][payload bytes...] +//! [requestId: SCALE str][trait: u8][method: u8][payload bytes...] //! ``` //! -//! The discriminant maps to a method/kind slot via the auto-generated -//! [`crate::generated::wire_table::WIRE_TABLE`]. Method ordering is part of -//! the wire protocol; only ever append to the table. The payload bytes are -//! the SCALE-encoded inner value, inlined without a length prefix. +//! The `(trait, method)` discriminant pair maps to a method/kind slot via the +//! auto-generated [`crate::generated::wire_table::WIRE_TABLE`]. Trait ids and +//! per-trait method ordering are part of the wire protocol; only ever append +//! within a trait. The payload bytes are the SCALE-encoded inner value, +//! inlined without a length prefix. //! -//! In-memory we keep the numeric id directly so dispatch does not need to +//! In-memory we keep the numeric pair directly so dispatch does not need to //! reconstruct string action tags on every frame. use parity_scale_codec::{Decode, Encode, Error as CodecError, Input, Output}; use crate::generated::wire_table::{RequestFrameIds, SubscriptionFrameIds, WIRE_TABLE, WireKind}; -/// Top-level wire message. Encoded as `[requestId][discriminant][bytes]`. +/// Top-level wire message. Encoded as `[requestId][trait][method][bytes]`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProtocolMessage { /// Per-message identifier carried by both halves of a request/response. @@ -74,7 +75,8 @@ pub fn encode_versioned_interrupt_payload(value: T, version: u8) -> V impl Encode for ProtocolMessage { fn encode_to(&self, dest: &mut T) { self.request_id.encode_to(dest); - self.payload.id.encode_to(dest); + self.payload.trait_id.encode_to(dest); + self.payload.method_id.encode_to(dest); // Payload bytes are inlined; the receiver reads "until end of frame" // because each transport frame is one ProtocolMessage. This matches // the public versioned enum transport shape (variant payload encoded @@ -89,9 +91,12 @@ impl Encode for ProtocolMessage { impl Decode for ProtocolMessage { fn decode(input: &mut I) -> Result { let request_id = String::decode(input)?; - let id = u8::decode(input)?; - // Unknown ids are accepted here; routing is deferred to dispatch, - // which drops frames with no registered handler. + let trait_id = u8::decode(input) + .map_err(|_| CodecError::from("frame is missing the trait discriminant byte"))?; + let method_id = u8::decode(input) + .map_err(|_| CodecError::from("frame is missing the method discriminant byte"))?; + // Unknown (trait, method) pairs are accepted here; routing is deferred + // to dispatch, which reports frames with no registered handler. let remaining = input .remaining_len()? .ok_or_else(|| CodecError::from("frame input must report remaining length"))?; @@ -99,23 +104,29 @@ impl Decode for ProtocolMessage { input.read(&mut value)?; Ok(ProtocolMessage { request_id, - payload: Payload { id, value }, + payload: Payload { + trait_id, + method_id, + value, + }, }) } } -/// Tagged payload. The `id` is the wire discriminant from -/// [`crate::generated::wire_table::WIRE_TABLE`], identifying the frame's method -/// and kind (request/response/start/stop/interrupt/receive). +/// Tagged payload. The `(trait_id, method_id)` pair is the wire discriminant +/// from [`crate::generated::wire_table::WIRE_TABLE`], identifying the frame's +/// trait, method, and kind (request/response/start/stop/interrupt/receive). /// /// Note: `Payload` does not derive `Encode`/`Decode` directly; the wire /// representation lives on [`ProtocolMessage`]. `Payload` is kept as a plain -/// data type for in-memory dispatch (key on `id`, value bytes already +/// data type for in-memory dispatch (key on the pair, value bytes already /// SCALE-encoded by the call site). #[derive(Debug, Clone, PartialEq, Eq)] pub struct Payload { - /// Wire discriminant identifying the frame's method and kind. - pub id: u8, + /// Trait discriminant: first byte of the wire pair. + pub trait_id: u8, + /// Method discriminant within the trait: second byte of the wire pair. + pub method_id: u8, /// SCALE-encoded inner value bytes. pub value: Vec, } @@ -190,56 +201,64 @@ mod tests { V1(T), } - fn build(id: u8, value: Vec) -> ProtocolMessage { + fn build(trait_id: u8, method_id: u8, value: Vec) -> ProtocolMessage { ProtocolMessage { request_id: "p:1".to_string(), - payload: Payload { id, value }, + payload: Payload { + trait_id, + method_id, + value, + }, } } - fn expected_wire(id: u8, value: &[u8]) -> Vec { + fn expected_wire(trait_id: u8, method_id: u8, value: &[u8]) -> Vec { let mut out = Vec::new(); "p:1".to_string().encode_to(&mut out); - out.push(id); + out.push(trait_id); + out.push(method_id); out.extend_from_slice(value); out } #[test] - fn handshake_request_encodes_with_discriminant_zero() { - // SCALE-encoded HostHandshakeRequest::V1(1u8) = [0u8 variant][1u8 codec_version] - let inner: Vec = vec![0x00, 0x01]; - let msg = build(0, inner.clone()); - assert_eq!(msg.encode(), expected_wire(0, &inner)); + fn handshake_request_encodes_with_discriminant_pair_zero_zero() { + // SCALE-encoded HostHandshakeRequest::V1(2u8) = [0u8 variant][2u8 codec_version] + let inner: Vec = vec![0x00, 0x02]; + let msg = build(0, 0, inner.clone()); + assert_eq!(msg.encode(), expected_wire(0, 0, &inner)); } #[test] - fn get_account_request_encodes_with_discriminant_22() { + fn get_account_request_encodes_with_discriminant_pair() { let mut inner = vec![0x00]; // V1 variant "foo".to_string().encode_to(&mut inner); 0u32.encode_to(&mut inner); - let msg = build(22, inner.clone()); - assert_eq!(msg.encode(), expected_wire(22, &inner)); + // account trait = 1, get_account request = 4. + let msg = build(1, 4, inner.clone()); + assert_eq!(msg.encode(), expected_wire(1, 4, &inner)); } #[test] - fn round_trip_preserves_id_and_value() { + fn round_trip_preserves_ids_and_value() { let inner: Vec = vec![0x00, 0x42, 0xab, 0xcd]; - let msg = build(12, inner.clone()); + let msg = build(6, 0, inner.clone()); let decoded = ProtocolMessage::decode(&mut &msg.encode()[..]).expect("decode"); assert_eq!(decoded, msg); } - /// An unknown discriminant is no longer rejected at decode; routing is - /// deferred to dispatch (which drops frames with no registered handler). + /// An unknown discriminant pair is not rejected at decode; routing is + /// deferred to dispatch (which reports frames with no registered handler). #[test] - fn unknown_discriminant_decodes_ok() { + fn unknown_discriminant_pair_decodes_ok() { let mut bytes = Vec::new(); "p:1".to_string().encode_to(&mut bytes); - bytes.push(250); // far outside the populated range + bytes.push(250); // far outside the populated trait range + bytes.push(123); bytes.extend_from_slice(&[0xaa, 0xbb]); - let decoded = ProtocolMessage::decode(&mut &bytes[..]).expect("unknown id must decode"); - assert_eq!(decoded.payload.id, 250); + let decoded = ProtocolMessage::decode(&mut &bytes[..]).expect("unknown pair must decode"); + assert_eq!(decoded.payload.trait_id, 250); + assert_eq!(decoded.payload.method_id, 123); assert_eq!(decoded.payload.value, vec![0xaa, 0xbb]); } @@ -247,25 +266,28 @@ mod tests { /// regression where `Decode` mishandles a frame whose payload is empty for /// `_stop` / `_interrupt` (no inner data) but non-empty for `_start` / /// `_receive`. The ids are the `account_connection_status_subscribe` - /// quartet (18..=21). + /// quartet (trait 1, methods 0..=3). #[test] fn subscription_phases_round_trip_through_codec() { let cases: &[(u8, Vec)] = &[ - (18, vec![0x00, 0xaa]), // start - (19, Vec::new()), // stop - (20, Vec::new()), // interrupt - (21, vec![0x01, 0x02, 0x03, 0x04]), // receive + (0, vec![0x00, 0xaa]), // start + (1, Vec::new()), // stop + (2, Vec::new()), // interrupt + (3, vec![0x01, 0x02, 0x03, 0x04]), // receive ]; - for (id, value) in cases { - let msg = build(*id, value.clone()); + for (method_id, value) in cases { + let msg = build(1, *method_id, value.clone()); let bytes = msg.encode(); assert_eq!( bytes, - expected_wire(*id, value), - "encode mismatch for id {id}" + expected_wire(1, *method_id, value), + "encode mismatch for method id {method_id}" ); let decoded = ProtocolMessage::decode(&mut &bytes[..]).expect("decode"); - assert_eq!(decoded, msg, "round-trip mismatch for id {id}"); + assert_eq!( + decoded, msg, + "round-trip mismatch for method id {method_id}" + ); } } @@ -274,18 +296,21 @@ mod tests { #[test] fn id_helpers_resolve_known_methods() { let handshake = request_ids("system_handshake").expect("known request method"); + assert_eq!(handshake.trait_id, 0); assert_eq!(handshake.request_id, 0); assert_eq!(handshake.response_id, 1); let get_account = request_ids("account_get_account").expect("known request method"); - assert_eq!(get_account.request_id, 22); + assert_eq!(get_account.trait_id, 1); + assert_eq!(get_account.request_id, 4); let sub = subscription_ids("account_connection_status_subscribe").expect("known subscription"); - assert_eq!(sub.start_id, 18); - assert_eq!(sub.stop_id, 19); - assert_eq!(sub.interrupt_id, 20); - assert_eq!(sub.receive_id, 21); + assert_eq!(sub.trait_id, 1); + assert_eq!(sub.start_id, 0); + assert_eq!(sub.stop_id, 1); + assert_eq!(sub.interrupt_id, 2); + assert_eq!(sub.receive_id, 3); // A request method is not a subscription and vice versa. assert!(subscription_ids("system_handshake").is_none()); @@ -297,11 +322,11 @@ mod tests { /// handle `remaining_len == 0` without erroring or reading past EOF. #[test] fn empty_payload_round_trips() { - // local_storage_clear_response = 17. - let msg = build(17, Vec::new()); + // local_storage_clear_response = (6, 5). + let msg = build(6, 5, Vec::new()); let bytes = msg.encode(); - // [SCALE compact-len 0x0c][p][:][1][u8 17] = 4 + 1 = 5 bytes total - assert_eq!(bytes.len(), 5); + // [SCALE compact-len 0x0c][p][:][1][u8 6][u8 5] = 4 + 2 = 6 bytes total + assert_eq!(bytes.len(), 6); let decoded = ProtocolMessage::decode(&mut &bytes[..]).expect("decode"); assert_eq!(decoded, msg); } @@ -314,7 +339,8 @@ mod tests { let msg = ProtocolMessage { request_id: long_id, payload: Payload { - id: 22, + trait_id: 1, + method_id: 4, value: vec![0x00, 0xab, 0xcd], }, }; @@ -322,15 +348,30 @@ mod tests { assert_eq!(decoded, msg); } - /// Truncated frames must surface a `CodecError`, not panic. + /// Truncated frames must surface a `CodecError`, not panic, and the + /// trait-byte and method-byte truncations report distinct errors. #[test] fn truncated_frames_error_cleanly() { // Empty buffer. assert!(ProtocolMessage::decode(&mut &[][..]).is_err()); - // Just the requestId, no discriminant byte. + // Just the requestId, no trait byte. let mut only_request_id = Vec::new(); "p:1".to_string().encode_to(&mut only_request_id); - assert!(ProtocolMessage::decode(&mut &only_request_id[..]).is_err()); + let err = ProtocolMessage::decode(&mut &only_request_id[..]) + .expect_err("missing trait byte must error"); + assert!( + format!("{err}").contains("trait discriminant"), + "unexpected error: {err}" + ); + // RequestId plus the trait byte, no method byte. + let mut missing_method = only_request_id.clone(); + missing_method.push(0); + let err = ProtocolMessage::decode(&mut &missing_method[..]) + .expect_err("missing method byte must error"); + assert!( + format!("{err}").contains("method discriminant"), + "unexpected error: {err}" + ); // RequestId header claims length=200 but the buffer is far shorter. let truncated_str_header = [200u8 << 2, 0x61, 0x62, 0x63]; assert!(ProtocolMessage::decode(&mut &truncated_str_header[..]).is_err()); @@ -344,12 +385,13 @@ mod tests { let msg = ProtocolMessage { request_id: String::new(), payload: Payload { - id: 22, + trait_id: 1, + method_id: 4, value: vec![0x00, 0x01, 0x02], }, }; let bytes = msg.encode(); - // [SCALE compact-len 0 = 0x00][discriminant][payload] + // [SCALE compact-len 0 = 0x00][trait][method][payload] assert_eq!(bytes[0], 0x00); let decoded = ProtocolMessage::decode(&mut &bytes[..]).expect("decode"); assert_eq!(decoded, msg); @@ -361,7 +403,8 @@ mod tests { let msg = ProtocolMessage { request_id: "héllo-世界-🦀".to_string(), payload: Payload { - id: 22, + trait_id: 1, + method_id: 4, value: vec![0x00, 0x01], }, }; @@ -374,7 +417,7 @@ mod tests { #[test] fn large_payload_round_trips() { let big = vec![0xa5u8; 100 * 1024]; - let msg = build(22, big); + let msg = build(1, 4, big); let decoded = ProtocolMessage::decode(&mut &msg.encode()[..]).expect("decode"); assert_eq!(decoded, msg); } diff --git a/rust/crates/truapi-server/src/generated/wire_table.rs b/rust/crates/truapi-server/src/generated/wire_table.rs index 7360d0427..ecb2619fa 100644 --- a/rust/crates/truapi-server/src/generated/wire_table.rs +++ b/rust/crates/truapi-server/src/generated/wire_table.rs @@ -2,31 +2,37 @@ //! //! Auto-generated by truapi-codegen. Do not edit. //! -//! Each method reserves either two ids (request/response) or four -//! (start/stop/interrupt/receive). The ids for each method are exposed -//! as a named const (`PREIMAGE_SUBMIT`, ...); [`WIRE_TABLE`] and the -//! generated dispatcher both reference those consts so the numbers live -//! in exactly one place. The table is sorted by request/start id. +//! Every frame carries a `(trait, method)` discriminant pair. Each +//! method reserves either two method ids (request/response) or four +//! (start/stop/interrupt/receive) within its trait. The ids for each +//! method are exposed as a named const (`PREIMAGE_SUBMIT`, ...); +//! [`WIRE_TABLE`] and the generated dispatcher both reference those +//! consts so the numbers live in exactly one place. The table is +//! sorted by (trait id, request/start id). /// Request method wire discriminants. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RequestFrameIds { - /// Discriminant for the request frame. + /// Trait discriminant carried by both frames. + pub trait_id: u8, + /// Method discriminant for the request frame. pub request_id: u8, - /// Discriminant for the response frame. + /// Method discriminant for the response frame. pub response_id: u8, } /// Subscription method wire discriminants. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SubscriptionFrameIds { - /// Discriminant for the start frame. + /// Trait discriminant carried by all four frames. + pub trait_id: u8, + /// Method discriminant for the start frame. pub start_id: u8, - /// Discriminant for the stop frame. + /// Method discriminant for the stop frame. pub stop_id: u8, - /// Discriminant for the interrupt frame (server-initiated termination). + /// Method discriminant for the interrupt frame (server-initiated termination). pub interrupt_id: u8, - /// Discriminant for each receive frame (a streamed item). + /// Method discriminant for each receive frame (a streamed item). pub receive_id: u8, } @@ -48,426 +54,492 @@ pub enum WireKind { /// Wire discriminants for `system_handshake`. pub const SYSTEM_HANDSHAKE: RequestFrameIds = RequestFrameIds { + trait_id: 0, request_id: 0, response_id: 1, }; /// Wire discriminants for `system_feature_supported`. pub const SYSTEM_FEATURE_SUPPORTED: RequestFrameIds = RequestFrameIds { + trait_id: 0, request_id: 2, response_id: 3, }; -/// Wire discriminants for `notifications_send_push_notification`. -pub const NOTIFICATIONS_SEND_PUSH_NOTIFICATION: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `system_navigate_to`. +pub const SYSTEM_NAVIGATE_TO: RequestFrameIds = RequestFrameIds { + trait_id: 0, request_id: 4, response_id: 5, }; -/// Wire discriminants for `system_navigate_to`. -pub const SYSTEM_NAVIGATE_TO: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_connection_status_subscribe`. +pub const ACCOUNT_CONNECTION_STATUS_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 1, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, +}; + +/// Wire discriminants for `account_get_account`. +pub const ACCOUNT_GET_ACCOUNT: RequestFrameIds = RequestFrameIds { + trait_id: 1, + request_id: 4, + response_id: 5, +}; + +/// Wire discriminants for `account_get_account_alias`. +pub const ACCOUNT_GET_ACCOUNT_ALIAS: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 6, response_id: 7, }; -/// Wire discriminants for `permissions_request_device_permission`. -pub const PERMISSIONS_REQUEST_DEVICE_PERMISSION: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_create_account_proof`. +pub const ACCOUNT_CREATE_ACCOUNT_PROOF: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 8, response_id: 9, }; -/// Wire discriminants for `permissions_request_remote_permission`. -pub const PERMISSIONS_REQUEST_REMOTE_PERMISSION: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_get_legacy_accounts`. +pub const ACCOUNT_GET_LEGACY_ACCOUNTS: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 10, response_id: 11, }; -/// Wire discriminants for `local_storage_read`. -pub const LOCAL_STORAGE_READ: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_get_user_id`. +pub const ACCOUNT_GET_USER_ID: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 12, response_id: 13, }; -/// Wire discriminants for `local_storage_write`. -pub const LOCAL_STORAGE_WRITE: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_request_login`. +pub const ACCOUNT_REQUEST_LOGIN: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 14, response_id: 15, }; -/// Wire discriminants for `local_storage_clear`. -pub const LOCAL_STORAGE_CLEAR: RequestFrameIds = RequestFrameIds { +/// Wire discriminants for `account_sign_vrf`. +pub const ACCOUNT_SIGN_VRF: RequestFrameIds = RequestFrameIds { + trait_id: 1, request_id: 16, response_id: 17, }; -/// Wire discriminants for `account_connection_status_subscribe`. -pub const ACCOUNT_CONNECTION_STATUS_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 18, - stop_id: 19, - interrupt_id: 20, - receive_id: 21, +/// Wire discriminants for `chain_follow_head_subscribe`. +pub const CHAIN_FOLLOW_HEAD_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 2, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, }; -/// Wire discriminants for `account_get_account`. -pub const ACCOUNT_GET_ACCOUNT: RequestFrameIds = RequestFrameIds { - request_id: 22, - response_id: 23, +/// Wire discriminants for `chain_get_head_header`. +pub const CHAIN_GET_HEAD_HEADER: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 4, + response_id: 5, }; -/// Wire discriminants for `account_get_account_alias`. -pub const ACCOUNT_GET_ACCOUNT_ALIAS: RequestFrameIds = RequestFrameIds { - request_id: 24, - response_id: 25, +/// Wire discriminants for `chain_get_head_body`. +pub const CHAIN_GET_HEAD_BODY: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 6, + response_id: 7, }; -/// Wire discriminants for `account_create_account_proof`. -pub const ACCOUNT_CREATE_ACCOUNT_PROOF: RequestFrameIds = RequestFrameIds { - request_id: 26, - response_id: 27, +/// Wire discriminants for `chain_get_head_storage`. +pub const CHAIN_GET_HEAD_STORAGE: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 8, + response_id: 9, }; -/// Wire discriminants for `account_get_legacy_accounts`. -pub const ACCOUNT_GET_LEGACY_ACCOUNTS: RequestFrameIds = RequestFrameIds { - request_id: 28, - response_id: 29, +/// Wire discriminants for `chain_call_head`. +pub const CHAIN_CALL_HEAD: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 10, + response_id: 11, }; -/// Wire discriminants for `signing_create_transaction`. -pub const SIGNING_CREATE_TRANSACTION: RequestFrameIds = RequestFrameIds { - request_id: 30, - response_id: 31, +/// Wire discriminants for `chain_unpin_head`. +pub const CHAIN_UNPIN_HEAD: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 12, + response_id: 13, }; -/// Wire discriminants for `signing_create_transaction_with_legacy_account`. -pub const SIGNING_CREATE_TRANSACTION_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { - request_id: 32, - response_id: 33, +/// Wire discriminants for `chain_continue_head`. +pub const CHAIN_CONTINUE_HEAD: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 14, + response_id: 15, }; -/// Wire discriminants for `signing_sign_raw_with_legacy_account`. -pub const SIGNING_SIGN_RAW_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { - request_id: 34, - response_id: 35, +/// Wire discriminants for `chain_stop_head_operation`. +pub const CHAIN_STOP_HEAD_OPERATION: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 16, + response_id: 17, }; -/// Wire discriminants for `signing_sign_payload_with_legacy_account`. -pub const SIGNING_SIGN_PAYLOAD_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { - request_id: 36, - response_id: 37, +/// Wire discriminants for `chain_get_spec_genesis_hash`. +pub const CHAIN_GET_SPEC_GENESIS_HASH: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 18, + response_id: 19, +}; + +/// Wire discriminants for `chain_get_spec_chain_name`. +pub const CHAIN_GET_SPEC_CHAIN_NAME: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 20, + response_id: 21, +}; + +/// Wire discriminants for `chain_get_spec_properties`. +pub const CHAIN_GET_SPEC_PROPERTIES: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 22, + response_id: 23, +}; + +/// Wire discriminants for `chain_broadcast_transaction`. +pub const CHAIN_BROADCAST_TRANSACTION: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 24, + response_id: 25, +}; + +/// Wire discriminants for `chain_stop_transaction`. +pub const CHAIN_STOP_TRANSACTION: RequestFrameIds = RequestFrameIds { + trait_id: 2, + request_id: 26, + response_id: 27, }; /// Wire discriminants for `chat_create_room`. pub const CHAT_CREATE_ROOM: RequestFrameIds = RequestFrameIds { - request_id: 38, - response_id: 39, + trait_id: 3, + request_id: 0, + response_id: 1, }; /// Wire discriminants for `chat_register_bot`. pub const CHAT_REGISTER_BOT: RequestFrameIds = RequestFrameIds { - request_id: 40, - response_id: 41, + trait_id: 3, + request_id: 2, + response_id: 3, }; /// Wire discriminants for `chat_list_subscribe`. pub const CHAT_LIST_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 42, - stop_id: 43, - interrupt_id: 44, - receive_id: 45, + trait_id: 3, + start_id: 4, + stop_id: 5, + interrupt_id: 6, + receive_id: 7, }; /// Wire discriminants for `chat_post_message`. pub const CHAT_POST_MESSAGE: RequestFrameIds = RequestFrameIds { - request_id: 46, - response_id: 47, + trait_id: 3, + request_id: 8, + response_id: 9, }; /// Wire discriminants for `chat_action_subscribe`. pub const CHAT_ACTION_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 48, - stop_id: 49, - interrupt_id: 50, - receive_id: 51, + trait_id: 3, + start_id: 10, + stop_id: 11, + interrupt_id: 12, + receive_id: 13, }; /// Wire discriminants for `chat_custom_message_render_subscribe`. pub const CHAT_CUSTOM_MESSAGE_RENDER_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 52, - stop_id: 53, - interrupt_id: 54, - receive_id: 55, -}; - -/// Wire discriminants for `statement_store_subscribe`. -pub const STATEMENT_STORE_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 56, - stop_id: 57, - interrupt_id: 58, - receive_id: 59, -}; - -/// Wire discriminants for `statement_store_create_proof`. -pub const STATEMENT_STORE_CREATE_PROOF: RequestFrameIds = RequestFrameIds { - request_id: 60, - response_id: 61, -}; - -/// Wire discriminants for `statement_store_submit`. -pub const STATEMENT_STORE_SUBMIT: RequestFrameIds = RequestFrameIds { - request_id: 62, - response_id: 63, -}; - -/// Wire discriminants for `preimage_lookup_subscribe`. -pub const PREIMAGE_LOOKUP_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 64, - stop_id: 65, - interrupt_id: 66, - receive_id: 67, -}; - -/// Wire discriminants for `preimage_submit`. -pub const PREIMAGE_SUBMIT: RequestFrameIds = RequestFrameIds { - request_id: 68, - response_id: 69, -}; - -/// Wire discriminants for `chain_follow_head_subscribe`. -pub const CHAIN_FOLLOW_HEAD_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 76, - stop_id: 77, - interrupt_id: 78, - receive_id: 79, -}; - -/// Wire discriminants for `chain_get_head_header`. -pub const CHAIN_GET_HEAD_HEADER: RequestFrameIds = RequestFrameIds { - request_id: 80, - response_id: 81, -}; - -/// Wire discriminants for `chain_get_head_body`. -pub const CHAIN_GET_HEAD_BODY: RequestFrameIds = RequestFrameIds { - request_id: 82, - response_id: 83, -}; - -/// Wire discriminants for `chain_get_head_storage`. -pub const CHAIN_GET_HEAD_STORAGE: RequestFrameIds = RequestFrameIds { - request_id: 84, - response_id: 85, -}; - -/// Wire discriminants for `chain_call_head`. -pub const CHAIN_CALL_HEAD: RequestFrameIds = RequestFrameIds { - request_id: 86, - response_id: 87, + trait_id: 3, + start_id: 14, + stop_id: 15, + interrupt_id: 16, + receive_id: 17, }; -/// Wire discriminants for `chain_unpin_head`. -pub const CHAIN_UNPIN_HEAD: RequestFrameIds = RequestFrameIds { - request_id: 88, - response_id: 89, +/// Wire discriminants for `coin_payment_create_purse`. +pub const COIN_PAYMENT_CREATE_PURSE: RequestFrameIds = RequestFrameIds { + trait_id: 4, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `chain_continue_head`. -pub const CHAIN_CONTINUE_HEAD: RequestFrameIds = RequestFrameIds { - request_id: 90, - response_id: 91, +/// Wire discriminants for `coin_payment_query_purse`. +pub const COIN_PAYMENT_QUERY_PURSE: RequestFrameIds = RequestFrameIds { + trait_id: 4, + request_id: 2, + response_id: 3, }; -/// Wire discriminants for `chain_stop_head_operation`. -pub const CHAIN_STOP_HEAD_OPERATION: RequestFrameIds = RequestFrameIds { - request_id: 92, - response_id: 93, +/// Wire discriminants for `coin_payment_rebalance_purse`. +pub const COIN_PAYMENT_REBALANCE_PURSE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 4, + start_id: 4, + stop_id: 5, + interrupt_id: 6, + receive_id: 7, }; -/// Wire discriminants for `chain_get_spec_genesis_hash`. -pub const CHAIN_GET_SPEC_GENESIS_HASH: RequestFrameIds = RequestFrameIds { - request_id: 94, - response_id: 95, +/// Wire discriminants for `coin_payment_delete_purse`. +pub const COIN_PAYMENT_DELETE_PURSE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 4, + start_id: 8, + stop_id: 9, + interrupt_id: 10, + receive_id: 11, }; -/// Wire discriminants for `chain_get_spec_chain_name`. -pub const CHAIN_GET_SPEC_CHAIN_NAME: RequestFrameIds = RequestFrameIds { - request_id: 96, - response_id: 97, +/// Wire discriminants for `coin_payment_create_receivable`. +pub const COIN_PAYMENT_CREATE_RECEIVABLE: RequestFrameIds = RequestFrameIds { + trait_id: 4, + request_id: 12, + response_id: 13, }; -/// Wire discriminants for `chain_get_spec_properties`. -pub const CHAIN_GET_SPEC_PROPERTIES: RequestFrameIds = RequestFrameIds { - request_id: 98, - response_id: 99, +/// Wire discriminants for `coin_payment_create_cheque`. +pub const COIN_PAYMENT_CREATE_CHEQUE: RequestFrameIds = RequestFrameIds { + trait_id: 4, + request_id: 14, + response_id: 15, }; -/// Wire discriminants for `chain_broadcast_transaction`. -pub const CHAIN_BROADCAST_TRANSACTION: RequestFrameIds = RequestFrameIds { - request_id: 100, - response_id: 101, +/// Wire discriminants for `coin_payment_deposit`. +pub const COIN_PAYMENT_DEPOSIT: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 4, + start_id: 16, + stop_id: 17, + interrupt_id: 18, + receive_id: 19, }; -/// Wire discriminants for `chain_stop_transaction`. -pub const CHAIN_STOP_TRANSACTION: RequestFrameIds = RequestFrameIds { - request_id: 102, - response_id: 103, +/// Wire discriminants for `coin_payment_refund`. +pub const COIN_PAYMENT_REFUND: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 4, + start_id: 20, + stop_id: 21, + interrupt_id: 22, + receive_id: 23, }; -/// Wire discriminants for `theme_subscribe`. -pub const THEME_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 104, - stop_id: 105, - interrupt_id: 106, - receive_id: 107, +/// Wire discriminants for `coin_payment_listen_for_payment`. +pub const COIN_PAYMENT_LISTEN_FOR_PAYMENT: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 4, + start_id: 24, + stop_id: 25, + interrupt_id: 26, + receive_id: 27, }; /// Wire discriminants for `entropy_derive`. pub const ENTROPY_DERIVE: RequestFrameIds = RequestFrameIds { - request_id: 108, - response_id: 109, + trait_id: 5, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `account_get_user_id`. -pub const ACCOUNT_GET_USER_ID: RequestFrameIds = RequestFrameIds { - request_id: 110, - response_id: 111, +/// Wire discriminants for `local_storage_read`. +pub const LOCAL_STORAGE_READ: RequestFrameIds = RequestFrameIds { + trait_id: 6, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `account_request_login`. -pub const ACCOUNT_REQUEST_LOGIN: RequestFrameIds = RequestFrameIds { - request_id: 112, - response_id: 113, +/// Wire discriminants for `local_storage_write`. +pub const LOCAL_STORAGE_WRITE: RequestFrameIds = RequestFrameIds { + trait_id: 6, + request_id: 2, + response_id: 3, }; -/// Wire discriminants for `signing_sign_raw`. -pub const SIGNING_SIGN_RAW: RequestFrameIds = RequestFrameIds { - request_id: 114, - response_id: 115, +/// Wire discriminants for `local_storage_clear`. +pub const LOCAL_STORAGE_CLEAR: RequestFrameIds = RequestFrameIds { + trait_id: 6, + request_id: 4, + response_id: 5, }; -/// Wire discriminants for `signing_sign_payload`. -pub const SIGNING_SIGN_PAYLOAD: RequestFrameIds = RequestFrameIds { - request_id: 116, - response_id: 117, +/// Wire discriminants for `notifications_send_push_notification`. +pub const NOTIFICATIONS_SEND_PUSH_NOTIFICATION: RequestFrameIds = RequestFrameIds { + trait_id: 7, + request_id: 0, + response_id: 1, +}; + +/// Wire discriminants for `notifications_cancel_push_notification`. +pub const NOTIFICATIONS_CANCEL_PUSH_NOTIFICATION: RequestFrameIds = RequestFrameIds { + trait_id: 7, + request_id: 2, + response_id: 3, }; /// Wire discriminants for `payment_balance_subscribe`. pub const PAYMENT_BALANCE_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 118, - stop_id: 119, - interrupt_id: 120, - receive_id: 121, + trait_id: 8, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, }; /// Wire discriminants for `payment_top_up`. pub const PAYMENT_TOP_UP: RequestFrameIds = RequestFrameIds { - request_id: 122, - response_id: 123, + trait_id: 8, + request_id: 4, + response_id: 5, }; /// Wire discriminants for `payment_request`. pub const PAYMENT_REQUEST: RequestFrameIds = RequestFrameIds { - request_id: 124, - response_id: 125, + trait_id: 8, + request_id: 6, + response_id: 7, }; /// Wire discriminants for `payment_status_subscribe`. pub const PAYMENT_STATUS_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 126, - stop_id: 127, - interrupt_id: 128, - receive_id: 129, + trait_id: 8, + start_id: 8, + stop_id: 9, + interrupt_id: 10, + receive_id: 11, }; -/// Wire discriminants for `resource_allocation_request`. -pub const RESOURCE_ALLOCATION_REQUEST: RequestFrameIds = RequestFrameIds { - request_id: 130, - response_id: 131, +/// Wire discriminants for `permissions_request_device_permission`. +pub const PERMISSIONS_REQUEST_DEVICE_PERMISSION: RequestFrameIds = RequestFrameIds { + trait_id: 9, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `statement_store_create_proof_authorized`. -pub const STATEMENT_STORE_CREATE_PROOF_AUTHORIZED: RequestFrameIds = RequestFrameIds { - request_id: 132, - response_id: 133, +/// Wire discriminants for `permissions_request_remote_permission`. +pub const PERMISSIONS_REQUEST_REMOTE_PERMISSION: RequestFrameIds = RequestFrameIds { + trait_id: 9, + request_id: 2, + response_id: 3, }; -/// Wire discriminants for `notifications_cancel_push_notification`. -pub const NOTIFICATIONS_CANCEL_PUSH_NOTIFICATION: RequestFrameIds = RequestFrameIds { - request_id: 134, - response_id: 135, +/// Wire discriminants for `preimage_lookup_subscribe`. +pub const PREIMAGE_LOOKUP_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 10, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, }; -/// Wire discriminants for `coin_payment_create_purse`. -pub const COIN_PAYMENT_CREATE_PURSE: RequestFrameIds = RequestFrameIds { - request_id: 136, - response_id: 137, +/// Wire discriminants for `preimage_submit`. +pub const PREIMAGE_SUBMIT: RequestFrameIds = RequestFrameIds { + trait_id: 10, + request_id: 4, + response_id: 5, }; -/// Wire discriminants for `coin_payment_query_purse`. -pub const COIN_PAYMENT_QUERY_PURSE: RequestFrameIds = RequestFrameIds { - request_id: 138, - response_id: 139, +/// Wire discriminants for `resource_allocation_request`. +pub const RESOURCE_ALLOCATION_REQUEST: RequestFrameIds = RequestFrameIds { + trait_id: 11, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `coin_payment_rebalance_purse`. -pub const COIN_PAYMENT_REBALANCE_PURSE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 140, - stop_id: 141, - interrupt_id: 142, - receive_id: 143, +/// Wire discriminants for `signing_create_transaction`. +pub const SIGNING_CREATE_TRANSACTION: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 0, + response_id: 1, }; -/// Wire discriminants for `coin_payment_delete_purse`. -pub const COIN_PAYMENT_DELETE_PURSE: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 144, - stop_id: 145, - interrupt_id: 146, - receive_id: 147, +/// Wire discriminants for `signing_create_transaction_with_legacy_account`. +pub const SIGNING_CREATE_TRANSACTION_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 2, + response_id: 3, }; -/// Wire discriminants for `coin_payment_create_receivable`. -pub const COIN_PAYMENT_CREATE_RECEIVABLE: RequestFrameIds = RequestFrameIds { - request_id: 148, - response_id: 149, +/// Wire discriminants for `signing_sign_raw_with_legacy_account`. +pub const SIGNING_SIGN_RAW_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 4, + response_id: 5, +}; + +/// Wire discriminants for `signing_sign_payload_with_legacy_account`. +pub const SIGNING_SIGN_PAYLOAD_WITH_LEGACY_ACCOUNT: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 6, + response_id: 7, +}; + +/// Wire discriminants for `signing_sign_raw`. +pub const SIGNING_SIGN_RAW: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 8, + response_id: 9, +}; + +/// Wire discriminants for `signing_sign_payload`. +pub const SIGNING_SIGN_PAYLOAD: RequestFrameIds = RequestFrameIds { + trait_id: 12, + request_id: 10, + response_id: 11, }; -/// Wire discriminants for `coin_payment_create_cheque`. -pub const COIN_PAYMENT_CREATE_CHEQUE: RequestFrameIds = RequestFrameIds { - request_id: 150, - response_id: 151, +/// Wire discriminants for `statement_store_subscribe`. +pub const STATEMENT_STORE_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 13, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, }; -/// Wire discriminants for `coin_payment_deposit`. -pub const COIN_PAYMENT_DEPOSIT: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 152, - stop_id: 153, - interrupt_id: 154, - receive_id: 155, +/// Wire discriminants for `statement_store_create_proof`. +pub const STATEMENT_STORE_CREATE_PROOF: RequestFrameIds = RequestFrameIds { + trait_id: 13, + request_id: 4, + response_id: 5, }; -/// Wire discriminants for `coin_payment_refund`. -pub const COIN_PAYMENT_REFUND: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 156, - stop_id: 157, - interrupt_id: 158, - receive_id: 159, +/// Wire discriminants for `statement_store_submit`. +pub const STATEMENT_STORE_SUBMIT: RequestFrameIds = RequestFrameIds { + trait_id: 13, + request_id: 6, + response_id: 7, }; -/// Wire discriminants for `coin_payment_listen_for_payment`. -pub const COIN_PAYMENT_LISTEN_FOR_PAYMENT: SubscriptionFrameIds = SubscriptionFrameIds { - start_id: 160, - stop_id: 161, - interrupt_id: 162, - receive_id: 163, +/// Wire discriminants for `statement_store_create_proof_authorized`. +pub const STATEMENT_STORE_CREATE_PROOF_AUTHORIZED: RequestFrameIds = RequestFrameIds { + trait_id: 13, + request_id: 8, + response_id: 9, }; -/// Wire discriminants for `account_sign_vrf`. -pub const ACCOUNT_SIGN_VRF: RequestFrameIds = RequestFrameIds { - request_id: 164, - response_id: 165, +/// Wire discriminants for `theme_subscribe`. +pub const THEME_SUBSCRIBE: SubscriptionFrameIds = SubscriptionFrameIds { + trait_id: 14, + start_id: 0, + stop_id: 1, + interrupt_id: 2, + receive_id: 3, }; -/// The full wire table. Ordering is part of the wire protocol; -/// only ever append. Removed methods leave their slot empty. +/// The full wire table. Trait ids and per-trait method ordering are +/// part of the wire protocol; only ever append within a trait. +/// Removed methods leave their slot empty. pub const WIRE_TABLE: &[WireEntry] = &[ WireEntry { method: "system_handshake", @@ -477,34 +549,10 @@ pub const WIRE_TABLE: &[WireEntry] = &[ method: "system_feature_supported", kind: WireKind::Request(SYSTEM_FEATURE_SUPPORTED), }, - WireEntry { - method: "notifications_send_push_notification", - kind: WireKind::Request(NOTIFICATIONS_SEND_PUSH_NOTIFICATION), - }, WireEntry { method: "system_navigate_to", kind: WireKind::Request(SYSTEM_NAVIGATE_TO), }, - WireEntry { - method: "permissions_request_device_permission", - kind: WireKind::Request(PERMISSIONS_REQUEST_DEVICE_PERMISSION), - }, - WireEntry { - method: "permissions_request_remote_permission", - kind: WireKind::Request(PERMISSIONS_REQUEST_REMOTE_PERMISSION), - }, - WireEntry { - method: "local_storage_read", - kind: WireKind::Request(LOCAL_STORAGE_READ), - }, - WireEntry { - method: "local_storage_write", - kind: WireKind::Request(LOCAL_STORAGE_WRITE), - }, - WireEntry { - method: "local_storage_clear", - kind: WireKind::Request(LOCAL_STORAGE_CLEAR), - }, WireEntry { method: "account_connection_status_subscribe", kind: WireKind::Subscription(ACCOUNT_CONNECTION_STATUS_SUBSCRIBE), @@ -526,64 +574,16 @@ pub const WIRE_TABLE: &[WireEntry] = &[ kind: WireKind::Request(ACCOUNT_GET_LEGACY_ACCOUNTS), }, WireEntry { - method: "signing_create_transaction", - kind: WireKind::Request(SIGNING_CREATE_TRANSACTION), - }, - WireEntry { - method: "signing_create_transaction_with_legacy_account", - kind: WireKind::Request(SIGNING_CREATE_TRANSACTION_WITH_LEGACY_ACCOUNT), - }, - WireEntry { - method: "signing_sign_raw_with_legacy_account", - kind: WireKind::Request(SIGNING_SIGN_RAW_WITH_LEGACY_ACCOUNT), - }, - WireEntry { - method: "signing_sign_payload_with_legacy_account", - kind: WireKind::Request(SIGNING_SIGN_PAYLOAD_WITH_LEGACY_ACCOUNT), - }, - WireEntry { - method: "chat_create_room", - kind: WireKind::Request(CHAT_CREATE_ROOM), - }, - WireEntry { - method: "chat_register_bot", - kind: WireKind::Request(CHAT_REGISTER_BOT), - }, - WireEntry { - method: "chat_list_subscribe", - kind: WireKind::Subscription(CHAT_LIST_SUBSCRIBE), - }, - WireEntry { - method: "chat_post_message", - kind: WireKind::Request(CHAT_POST_MESSAGE), - }, - WireEntry { - method: "chat_action_subscribe", - kind: WireKind::Subscription(CHAT_ACTION_SUBSCRIBE), - }, - WireEntry { - method: "chat_custom_message_render_subscribe", - kind: WireKind::Subscription(CHAT_CUSTOM_MESSAGE_RENDER_SUBSCRIBE), - }, - WireEntry { - method: "statement_store_subscribe", - kind: WireKind::Subscription(STATEMENT_STORE_SUBSCRIBE), - }, - WireEntry { - method: "statement_store_create_proof", - kind: WireKind::Request(STATEMENT_STORE_CREATE_PROOF), - }, - WireEntry { - method: "statement_store_submit", - kind: WireKind::Request(STATEMENT_STORE_SUBMIT), + method: "account_get_user_id", + kind: WireKind::Request(ACCOUNT_GET_USER_ID), }, WireEntry { - method: "preimage_lookup_subscribe", - kind: WireKind::Subscription(PREIMAGE_LOOKUP_SUBSCRIBE), + method: "account_request_login", + kind: WireKind::Request(ACCOUNT_REQUEST_LOGIN), }, WireEntry { - method: "preimage_submit", - kind: WireKind::Request(PREIMAGE_SUBMIT), + method: "account_sign_vrf", + kind: WireKind::Request(ACCOUNT_SIGN_VRF), }, WireEntry { method: "chain_follow_head_subscribe", @@ -638,28 +638,88 @@ pub const WIRE_TABLE: &[WireEntry] = &[ kind: WireKind::Request(CHAIN_STOP_TRANSACTION), }, WireEntry { - method: "theme_subscribe", - kind: WireKind::Subscription(THEME_SUBSCRIBE), + method: "chat_create_room", + kind: WireKind::Request(CHAT_CREATE_ROOM), + }, + WireEntry { + method: "chat_register_bot", + kind: WireKind::Request(CHAT_REGISTER_BOT), + }, + WireEntry { + method: "chat_list_subscribe", + kind: WireKind::Subscription(CHAT_LIST_SUBSCRIBE), + }, + WireEntry { + method: "chat_post_message", + kind: WireKind::Request(CHAT_POST_MESSAGE), + }, + WireEntry { + method: "chat_action_subscribe", + kind: WireKind::Subscription(CHAT_ACTION_SUBSCRIBE), + }, + WireEntry { + method: "chat_custom_message_render_subscribe", + kind: WireKind::Subscription(CHAT_CUSTOM_MESSAGE_RENDER_SUBSCRIBE), + }, + WireEntry { + method: "coin_payment_create_purse", + kind: WireKind::Request(COIN_PAYMENT_CREATE_PURSE), + }, + WireEntry { + method: "coin_payment_query_purse", + kind: WireKind::Request(COIN_PAYMENT_QUERY_PURSE), + }, + WireEntry { + method: "coin_payment_rebalance_purse", + kind: WireKind::Subscription(COIN_PAYMENT_REBALANCE_PURSE), + }, + WireEntry { + method: "coin_payment_delete_purse", + kind: WireKind::Subscription(COIN_PAYMENT_DELETE_PURSE), + }, + WireEntry { + method: "coin_payment_create_receivable", + kind: WireKind::Request(COIN_PAYMENT_CREATE_RECEIVABLE), + }, + WireEntry { + method: "coin_payment_create_cheque", + kind: WireKind::Request(COIN_PAYMENT_CREATE_CHEQUE), + }, + WireEntry { + method: "coin_payment_deposit", + kind: WireKind::Subscription(COIN_PAYMENT_DEPOSIT), + }, + WireEntry { + method: "coin_payment_refund", + kind: WireKind::Subscription(COIN_PAYMENT_REFUND), + }, + WireEntry { + method: "coin_payment_listen_for_payment", + kind: WireKind::Subscription(COIN_PAYMENT_LISTEN_FOR_PAYMENT), }, WireEntry { method: "entropy_derive", kind: WireKind::Request(ENTROPY_DERIVE), }, WireEntry { - method: "account_get_user_id", - kind: WireKind::Request(ACCOUNT_GET_USER_ID), + method: "local_storage_read", + kind: WireKind::Request(LOCAL_STORAGE_READ), }, WireEntry { - method: "account_request_login", - kind: WireKind::Request(ACCOUNT_REQUEST_LOGIN), + method: "local_storage_write", + kind: WireKind::Request(LOCAL_STORAGE_WRITE), }, WireEntry { - method: "signing_sign_raw", - kind: WireKind::Request(SIGNING_SIGN_RAW), + method: "local_storage_clear", + kind: WireKind::Request(LOCAL_STORAGE_CLEAR), }, WireEntry { - method: "signing_sign_payload", - kind: WireKind::Request(SIGNING_SIGN_PAYLOAD), + method: "notifications_send_push_notification", + kind: WireKind::Request(NOTIFICATIONS_SEND_PUSH_NOTIFICATION), + }, + WireEntry { + method: "notifications_cancel_push_notification", + kind: WireKind::Request(NOTIFICATIONS_CANCEL_PUSH_NOTIFICATION), }, WireEntry { method: "payment_balance_subscribe", @@ -678,55 +738,67 @@ pub const WIRE_TABLE: &[WireEntry] = &[ kind: WireKind::Subscription(PAYMENT_STATUS_SUBSCRIBE), }, WireEntry { - method: "resource_allocation_request", - kind: WireKind::Request(RESOURCE_ALLOCATION_REQUEST), + method: "permissions_request_device_permission", + kind: WireKind::Request(PERMISSIONS_REQUEST_DEVICE_PERMISSION), }, WireEntry { - method: "statement_store_create_proof_authorized", - kind: WireKind::Request(STATEMENT_STORE_CREATE_PROOF_AUTHORIZED), + method: "permissions_request_remote_permission", + kind: WireKind::Request(PERMISSIONS_REQUEST_REMOTE_PERMISSION), }, WireEntry { - method: "notifications_cancel_push_notification", - kind: WireKind::Request(NOTIFICATIONS_CANCEL_PUSH_NOTIFICATION), + method: "preimage_lookup_subscribe", + kind: WireKind::Subscription(PREIMAGE_LOOKUP_SUBSCRIBE), }, WireEntry { - method: "coin_payment_create_purse", - kind: WireKind::Request(COIN_PAYMENT_CREATE_PURSE), + method: "preimage_submit", + kind: WireKind::Request(PREIMAGE_SUBMIT), }, WireEntry { - method: "coin_payment_query_purse", - kind: WireKind::Request(COIN_PAYMENT_QUERY_PURSE), + method: "resource_allocation_request", + kind: WireKind::Request(RESOURCE_ALLOCATION_REQUEST), }, WireEntry { - method: "coin_payment_rebalance_purse", - kind: WireKind::Subscription(COIN_PAYMENT_REBALANCE_PURSE), + method: "signing_create_transaction", + kind: WireKind::Request(SIGNING_CREATE_TRANSACTION), }, WireEntry { - method: "coin_payment_delete_purse", - kind: WireKind::Subscription(COIN_PAYMENT_DELETE_PURSE), + method: "signing_create_transaction_with_legacy_account", + kind: WireKind::Request(SIGNING_CREATE_TRANSACTION_WITH_LEGACY_ACCOUNT), }, WireEntry { - method: "coin_payment_create_receivable", - kind: WireKind::Request(COIN_PAYMENT_CREATE_RECEIVABLE), + method: "signing_sign_raw_with_legacy_account", + kind: WireKind::Request(SIGNING_SIGN_RAW_WITH_LEGACY_ACCOUNT), }, WireEntry { - method: "coin_payment_create_cheque", - kind: WireKind::Request(COIN_PAYMENT_CREATE_CHEQUE), + method: "signing_sign_payload_with_legacy_account", + kind: WireKind::Request(SIGNING_SIGN_PAYLOAD_WITH_LEGACY_ACCOUNT), }, WireEntry { - method: "coin_payment_deposit", - kind: WireKind::Subscription(COIN_PAYMENT_DEPOSIT), + method: "signing_sign_raw", + kind: WireKind::Request(SIGNING_SIGN_RAW), }, WireEntry { - method: "coin_payment_refund", - kind: WireKind::Subscription(COIN_PAYMENT_REFUND), + method: "signing_sign_payload", + kind: WireKind::Request(SIGNING_SIGN_PAYLOAD), }, WireEntry { - method: "coin_payment_listen_for_payment", - kind: WireKind::Subscription(COIN_PAYMENT_LISTEN_FOR_PAYMENT), + method: "statement_store_subscribe", + kind: WireKind::Subscription(STATEMENT_STORE_SUBSCRIBE), }, WireEntry { - method: "account_sign_vrf", - kind: WireKind::Request(ACCOUNT_SIGN_VRF), + method: "statement_store_create_proof", + kind: WireKind::Request(STATEMENT_STORE_CREATE_PROOF), + }, + WireEntry { + method: "statement_store_submit", + kind: WireKind::Request(STATEMENT_STORE_SUBMIT), + }, + WireEntry { + method: "statement_store_create_proof_authorized", + kind: WireKind::Request(STATEMENT_STORE_CREATE_PROOF_AUTHORIZED), + }, + WireEntry { + method: "theme_subscribe", + kind: WireKind::Subscription(THEME_SUBSCRIBE), }, ]; diff --git a/rust/crates/truapi-server/src/host_core.rs b/rust/crates/truapi-server/src/host_core.rs index d226ea5e8..123b07b71 100644 --- a/rust/crates/truapi-server/src/host_core.rs +++ b/rust/crates/truapi-server/src/host_core.rs @@ -716,7 +716,8 @@ mod tests { let frame = ProtocolMessage { request_id: "theme:1".to_string(), payload: Payload { - id: ids.start_id, + trait_id: ids.trait_id, + method_id: ids.start_id, value: Vec::new(), }, }; diff --git a/rust/crates/truapi-server/src/native.rs b/rust/crates/truapi-server/src/native.rs index 51f3da5ff..d16a6bb8b 100644 --- a/rust/crates/truapi-server/src/native.rs +++ b/rust/crates/truapi-server/src/native.rs @@ -2022,7 +2022,8 @@ mod tests { let permission_frame = ProtocolMessage { request_id: "p:permission".into(), payload: Payload { - id: permission_ids.request_id, + trait_id: permission_ids.trait_id, + method_id: permission_ids.request_id, value: HostDevicePermissionRequest::V1( v01::HostDevicePermissionRequest::Camera, ) @@ -2048,7 +2049,8 @@ mod tests { let feature_frame = ProtocolMessage { request_id: "p:feature".into(), payload: Payload { - id: feature_ids.request_id, + trait_id: feature_ids.trait_id, + method_id: feature_ids.request_id, value: HostFeatureSupportedRequest::V1( v01::HostFeatureSupportedRequest::Chain { genesis_hash: vec![0u8; 32], @@ -2103,10 +2105,18 @@ mod tests { }); assert_eq!(feature_response.request_id, "p:feature"); - assert_eq!(feature_response.payload.id, feature_ids.response_id); + assert_eq!(feature_response.payload.trait_id, feature_ids.trait_id); + assert_eq!(feature_response.payload.method_id, feature_ids.response_id); assert_eq!(permission_response.request_id, "p:permission"); - assert_eq!(permission_response.payload.id, permission_ids.response_id); + assert_eq!( + permission_response.payload.trait_id, + permission_ids.trait_id + ); + assert_eq!( + permission_response.payload.method_id, + permission_ids.response_id + ); // [Ok 0x00][V1 0x00][granted=1] assert_eq!(permission_response.payload.value, vec![0x00, 0x00, 0x01]); diff --git a/rust/crates/truapi-server/src/subscription.rs b/rust/crates/truapi-server/src/subscription.rs index cd026bd0f..71dc22db3 100644 --- a/rust/crates/truapi-server/src/subscription.rs +++ b/rust/crates/truapi-server/src/subscription.rs @@ -146,6 +146,7 @@ impl SubscriptionManager { pub fn activate( &self, token: ReservationToken, + trait_id: u8, receive_id: u8, interrupt_id: u8, mut stream: SubscriptionStream, @@ -203,7 +204,8 @@ impl SubscriptionManager { stream_transport.send(ProtocolMessage { request_id: rid.clone(), payload: Payload { - id: receive_id, + trait_id, + method_id: receive_id, value, }, }) @@ -212,7 +214,8 @@ impl SubscriptionManager { stream_transport.send(ProtocolMessage { request_id: rid.clone(), payload: Payload { - id: interrupt_id, + trait_id, + method_id: interrupt_id, value, }, }); @@ -243,7 +246,8 @@ impl SubscriptionManager { transport.send(ProtocolMessage { request_id, payload: Payload { - id: interrupt_id, + trait_id, + method_id: interrupt_id, value: Vec::new(), }, }); @@ -258,13 +262,14 @@ impl SubscriptionManager { pub fn register( &self, request_id: String, + trait_id: u8, receive_id: u8, interrupt_id: u8, stream: SubscriptionStream, transport: Arc, ) { let token = self.reserve(request_id); - self.activate(token, receive_id, interrupt_id, stream, transport); + self.activate(token, trait_id, receive_id, interrupt_id, stream, transport); } /// Handle a `_stop` frame from the product side. Cancels a live @@ -397,7 +402,7 @@ mod tests { let transport_dyn: Arc = transport_typed.clone(); let manager = SubscriptionManager::new(thread_per_subscription_spawner()); let slow_stream: SubscriptionStream = Box::pin(stream::pending()); - manager.register("p:1".to_string(), 99, 98, slow_stream, transport_dyn); + manager.register("p:1".to_string(), 7, 99, 98, slow_stream, transport_dyn); manager.handle_stop("p:1"); // Give the worker thread a beat to observe the cancel. std::thread::sleep(std::time::Duration::from_millis(50)); @@ -415,15 +420,17 @@ mod tests { let transport_dyn: Arc = transport_typed.clone(); let manager = SubscriptionManager::new(thread_per_subscription_spawner()); let items = dummy_stream(vec![vec![0xaa], vec![0xbb]]); - manager.register("p:1".to_string(), 99, 98, items, transport_dyn); + manager.register("p:1".to_string(), 7, 99, 98, items, transport_dyn); let observed = transport_typed.wait_for(3, std::time::Duration::from_secs(2)); assert_eq!(observed, 3, "expected 2 receive frames + 1 interrupt"); let frames = transport_typed.sent(); - assert_eq!(frames[0].payload.id, 99); + assert_eq!(frames[0].payload.trait_id, 7); + assert_eq!(frames[0].payload.method_id, 99); assert_eq!(frames[0].payload.value, vec![0xaa]); - assert_eq!(frames[1].payload.id, 99); + assert_eq!(frames[1].payload.method_id, 99); assert_eq!(frames[1].payload.value, vec![0xbb]); - assert_eq!(frames[2].payload.id, 98); + assert_eq!(frames[2].payload.trait_id, 7); + assert_eq!(frames[2].payload.method_id, 98); assert_eq!(frames[2].payload.value, Vec::::new()); } @@ -436,7 +443,7 @@ mod tests { let transport_dyn: Arc = transport_typed.clone(); let manager = SubscriptionManager::new(thread_per_subscription_spawner()); let slow_stream: SubscriptionStream = Box::pin(stream::pending()); - manager.register("p:1".to_string(), 99, 98, slow_stream, transport_dyn); + manager.register("p:1".to_string(), 7, 99, 98, slow_stream, transport_dyn); manager.handle_stop("p:1"); // Second call must not panic and must not emit any frame. manager.handle_stop("p:1"); @@ -463,7 +470,7 @@ mod tests { let transport_dyn: Arc = transport_typed.clone(); let manager = SubscriptionManager::new(spawner); let items = dummy_stream(vec![vec![0xcc]]); - manager.register("p:1".to_string(), 99, 98, items, transport_dyn); + manager.register("p:1".to_string(), 7, 99, 98, items, transport_dyn); // Wait for the worker future to drain to completion so we know // the spawner closure ran on this path. @@ -486,7 +493,7 @@ mod tests { let token = manager.reserve("p:1".to_string()); manager.handle_stop("p:1"); let items = dummy_stream(vec![vec![0x01], vec![0x02]]); - manager.activate(token, 99, 98, items, transport_dyn); + manager.activate(token, 7, 99, 98, items, transport_dyn); std::thread::sleep(std::time::Duration::from_millis(50)); assert!( transport_typed.sent().is_empty(), @@ -507,11 +514,11 @@ mod tests { // First subscription never yields; the second reservation for the // same id must stop it. let pending: SubscriptionStream = Box::pin(stream::pending()); - manager.register("p:1".to_string(), 99, 98, pending, transport_dyn.clone()); + manager.register("p:1".to_string(), 7, 99, 98, pending, transport_dyn.clone()); // Second subscription yields one item then ends. let items = dummy_stream(vec![vec![0xaa]]); - manager.register("p:1".to_string(), 99, 98, items, transport_dyn); + manager.register("p:1".to_string(), 7, 99, 98, items, transport_dyn); // Exactly the second stream's frames appear: one receive + one // completion interrupt. The first (pending) stream contributes none. @@ -521,9 +528,11 @@ mod tests { "expected the second stream's receive + interrupt only" ); let frames = transport_typed.sent(); - assert_eq!(frames[0].payload.id, 99); + assert_eq!(frames[0].payload.trait_id, 7); + assert_eq!(frames[0].payload.method_id, 99); assert_eq!(frames[0].payload.value, vec![0xaa]); - assert_eq!(frames[1].payload.id, 98); + assert_eq!(frames[1].payload.trait_id, 7); + assert_eq!(frames[1].payload.method_id, 98); manager.handle_stop("p:1"); std::thread::sleep(std::time::Duration::from_millis(50)); @@ -544,7 +553,7 @@ mod tests { dropped: dropped.clone(), }); - manager.register("p:1".to_string(), 99, 98, stream, transport_dyn); + manager.register("p:1".to_string(), 7, 99, 98, stream, transport_dyn); manager.cancel_all(); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); diff --git a/rust/crates/truapi-server/src/ws_bridge.rs b/rust/crates/truapi-server/src/ws_bridge.rs index 00a1b240f..b8edcd296 100644 --- a/rust/crates/truapi-server/src/ws_bridge.rs +++ b/rust/crates/truapi-server/src/ws_bridge.rs @@ -631,7 +631,8 @@ mod tests { let request_frame = ProtocolMessage { request_id: "p:1".into(), payload: Payload { - id: ids.request_id, + trait_id: ids.trait_id, + method_id: ids.request_id, value: HostFeatureSupportedRequest::V1( v01::HostFeatureSupportedRequest::Chain { genesis_hash: vec![0u8; 32], @@ -657,7 +658,8 @@ mod tests { let response = ProtocolMessage::decode(&mut &response_bytes[..]).expect("decode response"); assert_eq!(response.request_id, "p:1"); - assert_eq!(response.payload.id, ids.response_id); + assert_eq!(response.payload.trait_id, ids.trait_id); + assert_eq!(response.payload.method_id, ids.response_id); // Wire payload is `Result`-shaped: // [Ok disc=0x00][V1 variant 0x00][supported=1] assert_eq!(response.payload.value, vec![0x00, 0x00, 0x01]); diff --git a/rust/crates/truapi-server/tests/golden_frame.rs b/rust/crates/truapi-server/tests/golden_frame.rs index 94a75050d..f6aa3f62c 100644 --- a/rust/crates/truapi-server/tests/golden_frame.rs +++ b/rust/crates/truapi-server/tests/golden_frame.rs @@ -9,9 +9,10 @@ //! payload = account_get_account_request, //! inner = HostAccountGetRequest::V1(("foo", 0u32)) //! -//! On the wire (14 bytes): +//! On the wire (15 bytes): //! [0c 70 3a 31] requestId = compact-len(3) + "p:1" -//! [16] discriminant 22 = account_get_account_request +//! [01] trait discriminant 1 = account +//! [04] method discriminant 4 = get_account request //! [00] versioned wrapper variant V1 //! [0c 66 6f 6f] "foo" //! [00 00 00 00] u32 = 0 @@ -38,7 +39,8 @@ fn golden_account_get_frame_decodes_to_expected_message() { let expected = ProtocolMessage { request_id: "p:1".to_string(), payload: Payload { - id: wire_table::ACCOUNT_GET_ACCOUNT.request_id, + trait_id: wire_table::ACCOUNT_GET_ACCOUNT.trait_id, + method_id: wire_table::ACCOUNT_GET_ACCOUNT.request_id, value: expected_inner, }, }; diff --git a/rust/crates/truapi-server/tests/snapshots/golden-account-get.bin b/rust/crates/truapi-server/tests/snapshots/golden-account-get.bin index c66be11b9bf19e8c751b7faa4996bf36cd7e90b4..d14ecd70a7c811793b3ce3ca6e1624e479aacd6a 100644 GIT binary patch literal 15 Ucmd-nurg$1Vc<#2&u0Jv02D(4Jpcdz literal 14 Tcmd-nurd^5;7QBRX8-~K6a)fJ diff --git a/rust/crates/truapi-server/tests/wire_result_shape.rs b/rust/crates/truapi-server/tests/wire_result_shape.rs index 4ce821934..51188b6fd 100644 --- a/rust/crates/truapi-server/tests/wire_result_shape.rs +++ b/rust/crates/truapi-server/tests/wire_result_shape.rs @@ -50,13 +50,15 @@ fn feature_supported_ok_response_uses_ok_discriminant() { let frame = ProtocolMessage { request_id: "p:1".into(), payload: Payload { - id: ids.request_id, + trait_id: ids.trait_id, + method_id: ids.request_id, value: request.encode(), }, }; let response = dispatch(&core, frame); assert_eq!(response.request_id, "p:1"); - assert_eq!(response.payload.id, ids.response_id); + assert_eq!(response.payload.trait_id, ids.trait_id); + assert_eq!(response.payload.method_id, ids.response_id); // Wire payload: [V1 disc=0x00][Ok disc=0x00][encoded response body]. let mut expected = vec![0x00u8, 0x00u8]; @@ -78,13 +80,15 @@ fn local_storage_read_err_response_uses_err_discriminant() { let frame = ProtocolMessage { request_id: "p:2".into(), payload: Payload { - id: ids.request_id, + trait_id: ids.trait_id, + method_id: ids.request_id, value: request.encode(), }, }; let response = dispatch(&core, frame); assert_eq!(response.request_id, "p:2"); - assert_eq!(response.payload.id, ids.response_id); + assert_eq!(response.payload.trait_id, ids.trait_id); + assert_eq!(response.payload.method_id, ids.response_id); // Wire payload: // [V1 disc=0x00][Err disc=0x01][CallError::Domain][V1 error][encoded error body]. @@ -133,13 +137,15 @@ fn assert_request_returns_domain_error( ProtocolMessage { request_id: request_id.into(), payload: Payload { - id: ids.request_id, + trait_id: ids.trait_id, + method_id: ids.request_id, value, }, }, ); assert_eq!(response.request_id, request_id); - assert_eq!(response.payload.id, ids.response_id); + assert_eq!(response.payload.trait_id, ids.trait_id); + assert_eq!(response.payload.method_id, ids.response_id); assert_eq!(response.payload.value, versioned_result_err_payload(error)); } @@ -158,7 +164,8 @@ fn assert_subscription_start_interrupts_error( ProtocolMessage { request_id: request_id.into(), payload: Payload { - id: ids.start_id, + trait_id: ids.trait_id, + method_id: ids.start_id, value, }, }, @@ -168,7 +175,8 @@ fn assert_subscription_start_interrupts_error( let sent = transport.sent.lock().unwrap(); assert_eq!(sent.len(), 1); assert_eq!(sent[0].request_id, request_id); - assert_eq!(sent[0].payload.id, ids.interrupt_id); + assert_eq!(sent[0].payload.trait_id, ids.trait_id); + assert_eq!(sent[0].payload.method_id, ids.interrupt_id); assert_eq!( sent[0].payload.value, versioned_interrupt_err_payload(error) @@ -200,13 +208,15 @@ fn account_proof_declined_confirmation_returns_rejected() { ProtocolMessage { request_id: "p:account-proof".into(), payload: Payload { - id: ids.request_id, + trait_id: ids.trait_id, + method_id: ids.request_id, value: request.encode(), }, }, ); assert_eq!(response.request_id, "p:account-proof"); - assert_eq!(response.payload.id, ids.response_id); + assert_eq!(response.payload.trait_id, ids.trait_id); + assert_eq!(response.payload.method_id, ids.response_id); // The wire-shape platform declines the confirmation prompt, so the proof // request maps to a `Rejected` domain error in the standard Result-Err envelope. let expected = versioned_result_err_payload(account::HostAccountCreateProofError::V1( @@ -315,7 +325,8 @@ fn malformed_result_subscription_start_interrupts_with_malformed_frame() { ProtocolMessage { request_id: "p:malformed-sub".into(), payload: Payload { - id: ids.start_id, + trait_id: ids.trait_id, + method_id: ids.start_id, value: vec![0xff], }, }, @@ -325,7 +336,8 @@ fn malformed_result_subscription_start_interrupts_with_malformed_frame() { let sent = transport.sent.lock().unwrap(); assert_eq!(sent.len(), 1); assert_eq!(sent[0].request_id, "p:malformed-sub"); - assert_eq!(sent[0].payload.id, ids.interrupt_id); + assert_eq!(sent[0].payload.trait_id, ids.trait_id); + assert_eq!(sent[0].payload.method_id, ids.interrupt_id); assert_eq!(sent[0].payload.value.first(), Some(&0x00)); let mut payload = &sent[0].payload.value[1..]; @@ -392,7 +404,8 @@ fn subscription_start_receive_stop_through_wire_boundary() { let start = ProtocolMessage { request_id: "p:1".into(), payload: Payload { - id: ids.start_id, + trait_id: ids.trait_id, + method_id: ids.start_id, value: Vec::new(), }, }; @@ -404,14 +417,22 @@ fn subscription_start_receive_stop_through_wire_boundary() { assert!(Instant::now() < deadline, "no initial _receive frame"); std::thread::sleep(Duration::from_millis(10)); } - assert_eq!(transport.sent.lock().unwrap()[0].payload.id, ids.receive_id); + assert_eq!( + transport.sent.lock().unwrap()[0].payload.trait_id, + ids.trait_id + ); + assert_eq!( + transport.sent.lock().unwrap()[0].payload.method_id, + ids.receive_id + ); // Stop the subscription, then push a session change. A live subscription // would emit a Connected `_receive`; a stopped one must stay silent. let stop = ProtocolMessage { request_id: "p:1".into(), payload: Payload { - id: ids.stop_id, + trait_id: ids.trait_id, + method_id: ids.stop_id, value: Vec::new(), }, }; diff --git a/rust/crates/truapi-server/tests/wire_table_ts_parity.rs b/rust/crates/truapi-server/tests/wire_table_ts_parity.rs index e349139c9..624dcde64 100644 --- a/rust/crates/truapi-server/tests/wire_table_ts_parity.rs +++ b/rust/crates/truapi-server/tests/wire_table_ts_parity.rs @@ -1,6 +1,6 @@ //! Cross-language parity check: the Rust `WIRE_TABLE` and the TS -//! `wire-table.ts` must list the exact same `(method, request_id, response_id)` -//! tuples in the same order. A drift here means a product built against one +//! `wire-table.ts` must list the exact same +//! `(method, trait_id, request_id, response_id)` tuples in the same order. A drift here means a product built against one //! side will fail to decode frames produced by the other. //! //! Both files are auto-generated text artifacts of `truapi-codegen`; the @@ -20,6 +20,7 @@ const RUST_TABLE: &str = include_str!("../src/generated/wire_table.rs"); #[derive(Debug, PartialEq, Eq)] struct Row { method: String, + trait_id: u8, request_or_start: u8, response_or_receive: u8, /// Subscription `_stop` / `_interrupt` ids; `None` for request methods. @@ -59,6 +60,7 @@ fn parse_rust(src: &str) -> Vec { continue; } let method = rest[..colon].trim().to_ascii_lowercase(); + let mut trait_id = None; let mut request_or_start = None; let mut response_or_receive = None; let mut stop = None; @@ -68,6 +70,9 @@ fn parse_rust(src: &str) -> Vec { if t.starts_with("};") { break; } + if let Some(rest) = t.strip_prefix("trait_id: ") { + trait_id = Some(parse_id(rest, &method)); + } if let Some(rest) = t .strip_prefix("request_id: ") .or_else(|| t.strip_prefix("start_id: ")) @@ -89,7 +94,9 @@ fn parse_rust(src: &str) -> Vec { } if let (Some(rs), Some(rr)) = (request_or_start, response_or_receive) { out.push(Row { - method, + method: method.clone(), + trait_id: trait_id + .unwrap_or_else(|| panic!("missing trait_id for `{method}` in Rust table")), request_or_start: rs, response_or_receive: rr, stop, @@ -116,6 +123,7 @@ fn parse_ts(src: &str) -> Vec { continue; }; let method = rest[..name_end].to_ascii_lowercase(); + let mut trait_id = None; let mut request_or_start = None; let mut response_or_receive = None; let mut stop = None; @@ -126,6 +134,9 @@ fn parse_ts(src: &str) -> Vec { if t.starts_with("start:") || t.contains("SubscriptionFrameIds") { is_subscription = true; } + if let Some(rest) = t.strip_prefix("trait: ") { + trait_id = Some(parse_id(rest, &method)); + } if let Some(rest) = t .strip_prefix("request: ") .or_else(|| t.strip_prefix("start: ")) @@ -147,6 +158,9 @@ fn parse_ts(src: &str) -> Vec { if t.starts_with("} as const") || t == "}" { if let (Some(rs), Some(rr)) = (request_or_start, response_or_receive) { out.push(Row { + trait_id: trait_id.unwrap_or_else(|| { + panic!("missing trait id for `{method}` in TS table") + }), method, request_or_start: rs, response_or_receive: rr, diff --git a/rust/crates/truapi/README.md b/rust/crates/truapi/README.md index e47929328..5edb6a7cd 100644 --- a/rust/crates/truapi/README.md +++ b/rust/crates/truapi/README.md @@ -23,7 +23,7 @@ The crate has two layers: 1. **Protocol types** under `v01`. 2. **Unified host contract** under `api`, where each method takes a `CallContext`, a versioned request type, and returns a versioned response with `CallError` or a `Subscription`. -Wire ids are part of the public protocol after F1: existing ids are append-only. Do not renumber or reuse them. The generated Rust dispatcher and the generated TypeScript wire table must stay byte-compatible with deployed products. +Wire ids are part of the public protocol. Every frame carries a two-byte `(trait, method)` discriminant pair: the trait id comes from the trait-level `#[wire_trait(id = N)]` annotation and the method id from the method-level `#[wire(...)]` annotation. Trait ids and existing method ids are append-only **per trait**: never renumber or reuse an id within a trait, and never reassign a trait id. New methods take the next free method ids in their own trait without affecting any other trait. The generated Rust dispatcher and the generated TypeScript wire table must stay byte-compatible with deployed products. ## Key modules diff --git a/rust/crates/truapi/src/api/account.rs b/rust/crates/truapi/src/api/account.rs index dd8a448c2..9fbaf25e8 100644 --- a/rust/crates/truapi/src/api/account.rs +++ b/rust/crates/truapi/src/api/account.rs @@ -10,10 +10,11 @@ use crate::versioned::account::{ HostGetUserIdRequest, HostGetUserIdResponse, HostRequestLoginError, HostRequestLoginRequest, HostRequestLoginResponse, }; -use crate::wire; use crate::{CallContext, CallError, Subscription}; +use crate::{wire, wire_trait}; /// Account lookup, aliasing, and proof generation. +#[wire_trait(id = 1)] #[crate::async_trait] pub trait Account: Send + Sync { /// Subscribe to account connection status changes. @@ -26,7 +27,7 @@ pub trait Account: Send + Sync { /// ); /// console.log("connection status:", status); /// ``` - #[wire(start_id = 18)] + #[wire(start_id = 0)] async fn connection_status_subscribe( &self, _cx: &CallContext, @@ -55,7 +56,7 @@ pub trait Account: Send + Sync { /// assert(otherProduct.isOk(), "cross-product getAccount was denied or failed:", otherProduct); /// console.log("other product account retrieved after approval:", otherProduct.value); /// ``` - #[wire(request_id = 22)] + #[wire(request_id = 4)] async fn get_account( &self, _cx: &CallContext, @@ -85,7 +86,7 @@ pub trait Account: Send + Sync { /// assert(result.isOk(), "getAccountAlias failed:", result); /// console.log("account alias:", result.value); /// ``` - #[wire(request_id = 24)] + #[wire(request_id = 6)] async fn get_account_alias( &self, _cx: &CallContext, @@ -116,7 +117,7 @@ pub trait Account: Send + Sync { /// assert(result.isOk(), "createAccountProof failed:", result); /// console.log("account proof created:", result.value); /// ``` - #[wire(request_id = 26)] + #[wire(request_id = 8)] async fn create_account_proof( &self, _cx: &CallContext, @@ -147,7 +148,7 @@ pub trait Account: Send + Sync { /// assert(result.isOk(), "signVrf failed:", result); /// console.log("vrf signature:", result.value); /// ``` - #[wire(request_id = 164)] + #[wire(request_id = 16)] async fn sign_vrf( &self, _cx: &CallContext, @@ -166,7 +167,7 @@ pub trait Account: Send + Sync { /// assert(result.value.accounts.length === 0, "unexpected legacy accounts:", result.value); /// console.log("legacy accounts:", result.value.accounts); /// ``` - #[wire(request_id = 28)] + #[wire(request_id = 10)] async fn get_legacy_accounts( &self, _cx: &CallContext, @@ -182,7 +183,7 @@ pub trait Account: Send + Sync { /// assert(result.isOk(), "getUserId failed:", result); /// console.log("user id:", result.value); /// ``` - #[wire(request_id = 110)] + #[wire(request_id = 12)] async fn get_user_id( &self, _cx: &CallContext, @@ -203,7 +204,7 @@ pub trait Account: Send + Sync { /// assert(result.isOk(), "requestLogin failed:", result); /// console.log("login completed:", result.value); /// ``` - #[wire(request_id = 112)] + #[wire(request_id = 14)] async fn request_login( &self, _cx: &CallContext, diff --git a/rust/crates/truapi/src/api/chain.rs b/rust/crates/truapi/src/api/chain.rs index a37a7cf14..da6cfcc33 100644 --- a/rust/crates/truapi/src/api/chain.rs +++ b/rust/crates/truapi/src/api/chain.rs @@ -18,10 +18,11 @@ use crate::versioned::chain::{ RemoteChainTransactionStopError, RemoteChainTransactionStopRequest, RemoteChainTransactionStopResponse, }; -use crate::wire; use crate::{CallContext, CallError, Subscription}; +use crate::{wire, wire_trait}; /// Chain interaction methods. +#[wire_trait(id = 2)] #[crate::async_trait] pub trait Chain: Send + Sync { /// Follow the chain head and receive block events. @@ -43,7 +44,7 @@ pub trait Chain: Send + Sync { /// ); /// console.log("head follow event:", item); /// ``` - #[wire(start_id = 76)] + #[wire(start_id = 0)] async fn follow_head_subscribe( &self, _cx: &CallContext, @@ -71,7 +72,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "getHeadHeader failed:", result); /// console.log("block header:", result.value); /// ``` - #[wire(request_id = 80)] + #[wire(request_id = 4)] async fn get_head_header( &self, _cx: &CallContext, @@ -99,7 +100,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "getHeadBody failed:", result); /// console.log("block body:", result.value); /// ``` - #[wire(request_id = 82)] + #[wire(request_id = 6)] async fn get_head_body( &self, _cx: &CallContext, @@ -132,7 +133,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "getHeadStorage failed:", result); /// console.log("storage value:", result.value); /// ``` - #[wire(request_id = 84)] + #[wire(request_id = 8)] async fn get_head_storage( &self, _cx: &CallContext, @@ -167,7 +168,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "callHead failed:", result); /// console.log("runtime call result:", result.value); /// ``` - #[wire(request_id = 86)] + #[wire(request_id = 10)] async fn call_head( &self, _cx: &CallContext, @@ -199,7 +200,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "unpinHead failed:", result); /// console.log("blocks unpinned"); /// ``` - #[wire(request_id = 88)] + #[wire(request_id = 12)] async fn unpin_head( &self, _cx: &CallContext, @@ -231,7 +232,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "continueHead failed:", result); /// console.log("operation continued"); /// ``` - #[wire(request_id = 90)] + #[wire(request_id = 14)] async fn continue_head( &self, _cx: &CallContext, @@ -263,7 +264,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "stopHeadOperation failed:", result); /// console.log("operation stopped"); /// ``` - #[wire(request_id = 92)] + #[wire(request_id = 16)] async fn stop_head_operation( &self, _cx: &CallContext, @@ -284,7 +285,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "getSpecGenesisHash failed:", result); /// console.log("genesis hash:", result.value); /// ``` - #[wire(request_id = 94)] + #[wire(request_id = 18)] async fn get_spec_genesis_hash( &self, _cx: &CallContext, @@ -305,7 +306,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "getSpecChainName failed:", result); /// console.log("chain name:", result.value); /// ``` - #[wire(request_id = 96)] + #[wire(request_id = 20)] async fn get_spec_chain_name( &self, _cx: &CallContext, @@ -325,7 +326,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "getSpecProperties failed:", result); /// console.log("chain properties:", result.value); /// ``` - #[wire(request_id = 98)] + #[wire(request_id = 22)] async fn get_spec_properties( &self, _cx: &CallContext, @@ -346,7 +347,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "broadcastTransaction failed:", result); /// console.log("transaction broadcast:", result.value); /// ``` - #[wire(request_id = 100)] + #[wire(request_id = 24)] async fn broadcast_transaction( &self, _cx: &CallContext, @@ -380,7 +381,7 @@ pub trait Chain: Send + Sync { /// assert(result.isOk(), "stopTransaction failed:", result); /// console.log("transaction broadcast stopped"); /// ``` - #[wire(request_id = 102)] + #[wire(request_id = 26)] async fn stop_transaction( &self, _cx: &CallContext, diff --git a/rust/crates/truapi/src/api/chat.rs b/rust/crates/truapi/src/api/chat.rs index c9e9e8265..361b6dcc6 100644 --- a/rust/crates/truapi/src/api/chat.rs +++ b/rust/crates/truapi/src/api/chat.rs @@ -7,10 +7,11 @@ use crate::versioned::chat::{ HostChatRegisterBotRequest, HostChatRegisterBotResponse, ProductChatCustomMessageRenderSubscribeItem, ProductChatCustomMessageRenderSubscribeRequest, }; -use crate::wire; use crate::{CallContext, CallError, Subscription}; +use crate::{wire, wire_trait}; /// Chat room, bot, and message APIs. +#[wire_trait(id = 3)] #[crate::async_trait] pub trait Chat: Send + Sync { /// Create a chat room. @@ -24,7 +25,7 @@ pub trait Chat: Send + Sync { /// assert(result.isOk(), "createRoom failed:", result); /// console.log("room created:", result.value); /// ``` - #[wire(request_id = 38)] + #[wire(request_id = 0)] async fn create_room( &self, _cx: &CallContext, @@ -44,7 +45,7 @@ pub trait Chat: Send + Sync { /// assert(result.isOk(), "registerBot failed:", result); /// console.log("bot registered:", result.value); /// ``` - #[wire(request_id = 40)] + #[wire(request_id = 2)] async fn register_bot( &self, _cx: &CallContext, @@ -63,7 +64,7 @@ pub trait Chat: Send + Sync { /// ); /// console.log("room list received:", item); /// ``` - #[wire(start_id = 42)] + #[wire(start_id = 4)] async fn list_subscribe(&self, _cx: &CallContext) -> Subscription { Subscription::empty() } @@ -78,7 +79,7 @@ pub trait Chat: Send + Sync { /// assert(result.isOk(), "postMessage failed:", result); /// console.log("message posted:", result.value); /// ``` - #[wire(request_id = 46)] + #[wire(request_id = 8)] async fn post_message( &self, _cx: &CallContext, @@ -97,7 +98,7 @@ pub trait Chat: Send + Sync { /// ); /// console.log("action received:", item); /// ``` - #[wire(start_id = 48)] + #[wire(start_id = 10)] async fn action_subscribe( &self, _cx: &CallContext, @@ -125,7 +126,7 @@ pub trait Chat: Send + Sync { /// ); /// console.log("render request received:", item); /// ``` - #[wire(start_id = 52)] + #[wire(start_id = 14)] async fn custom_message_render_subscribe( &self, _cx: &CallContext, diff --git a/rust/crates/truapi/src/api/coin_payment.rs b/rust/crates/truapi/src/api/coin_payment.rs index 5839b8e3c..e33592c67 100644 --- a/rust/crates/truapi/src/api/coin_payment.rs +++ b/rust/crates/truapi/src/api/coin_payment.rs @@ -14,14 +14,15 @@ use crate::versioned::coin_payment::{ HostCoinPaymentRebalancePurseRequest, HostCoinPaymentRefundError, HostCoinPaymentRefundItem, HostCoinPaymentRefundRequest, }; -use crate::wire; use crate::{CallContext, CallError, Subscription}; +use crate::{wire, wire_trait}; /// CoinPayment operations. /// /// RFC 0017 describes `Resolvable` values for long-running operations. /// TrUAPI represents those as subscriptions whose items are the RFC status /// updates. +#[wire_trait(id = 4)] #[crate::async_trait] pub trait CoinPayment: Send + Sync { /// Create a new firewalled CoinPayment purse. @@ -33,7 +34,7 @@ pub trait CoinPayment: Send + Sync { /// assert(result.isOk(), "createPurse failed:", result); /// console.log("purse created:", result.value.purse); /// ``` - #[wire(request_id = 136)] + #[wire(request_id = 0)] async fn create_purse( &self, _cx: &CallContext, @@ -50,7 +51,7 @@ pub trait CoinPayment: Send + Sync { /// assert(result.isOk(), "queryPurse failed:", result); /// console.log("purse info:", result.value.info); /// ``` - #[wire(request_id = 138)] + #[wire(request_id = 2)] async fn query_purse( &self, _cx: &CallContext, @@ -73,7 +74,7 @@ pub trait CoinPayment: Send + Sync { /// ); /// console.log("rebalance status:", status); /// ``` - #[wire(start_id = 140)] + #[wire(start_id = 4)] async fn rebalance_purse( &self, _cx: &CallContext, @@ -99,7 +100,7 @@ pub trait CoinPayment: Send + Sync { /// ); /// console.log("delete status:", status); /// ``` - #[wire(start_id = 144)] + #[wire(start_id = 8)] async fn delete_purse( &self, _cx: &CallContext, @@ -118,7 +119,7 @@ pub trait CoinPayment: Send + Sync { /// assert(result.isOk(), "createReceivable failed:", result); /// console.log("receivable created:", result.value.receivable); /// ``` - #[wire(request_id = 148)] + #[wire(request_id = 12)] async fn create_receivable( &self, _cx: &CallContext, @@ -141,7 +142,7 @@ pub trait CoinPayment: Send + Sync { /// assert(result.isOk(), "createCheque failed:", result); /// console.log("cheque created:", result.value.cheque); /// ``` - #[wire(request_id = 150)] + #[wire(request_id = 14)] async fn create_cheque( &self, _cx: &CallContext, @@ -168,7 +169,7 @@ pub trait CoinPayment: Send + Sync { /// ); /// console.log("deposit status:", status); /// ``` - #[wire(start_id = 152)] + #[wire(start_id = 16)] async fn deposit( &self, _cx: &CallContext, @@ -195,7 +196,7 @@ pub trait CoinPayment: Send + Sync { /// ); /// console.log("refund status:", status); /// ``` - #[wire(start_id = 156)] + #[wire(start_id = 20)] async fn refund( &self, _cx: &CallContext, @@ -222,7 +223,7 @@ pub trait CoinPayment: Send + Sync { /// ); /// console.log("payment received:", item); /// ``` - #[wire(start_id = 160)] + #[wire(start_id = 24)] async fn listen_for_payment( &self, _cx: &CallContext, diff --git a/rust/crates/truapi/src/api/entropy.rs b/rust/crates/truapi/src/api/entropy.rs index 32f510b9b..cbad2ca66 100644 --- a/rust/crates/truapi/src/api/entropy.rs +++ b/rust/crates/truapi/src/api/entropy.rs @@ -3,10 +3,11 @@ use crate::versioned::entropy::{ HostDeriveEntropyError, HostDeriveEntropyRequest, HostDeriveEntropyResponse, }; -use crate::wire; use crate::{CallContext, CallError}; +use crate::{wire, wire_trait}; /// Deterministic entropy derivation. +#[wire_trait(id = 5)] #[crate::async_trait] pub trait Entropy: Send + Sync { /// Derive deterministic entropy. @@ -18,7 +19,7 @@ pub trait Entropy: Send + Sync { /// assert(result.isOk(), "derive failed:", result); /// console.log("entropy derived:", result.value); /// ``` - #[wire(request_id = 108)] + #[wire(request_id = 0)] async fn derive( &self, _cx: &CallContext, diff --git a/rust/crates/truapi/src/api/local_storage.rs b/rust/crates/truapi/src/api/local_storage.rs index ec0bc6343..1f1dc92a4 100644 --- a/rust/crates/truapi/src/api/local_storage.rs +++ b/rust/crates/truapi/src/api/local_storage.rs @@ -5,10 +5,11 @@ use crate::versioned::local_storage::{ HostLocalStorageReadError, HostLocalStorageReadRequest, HostLocalStorageReadResponse, HostLocalStorageWriteError, HostLocalStorageWriteRequest, HostLocalStorageWriteResponse, }; -use crate::wire; use crate::{CallContext, CallError}; +use crate::{wire, wire_trait}; /// Local key/value storage scoped to the calling product. +#[wire_trait(id = 6)] #[crate::async_trait] pub trait LocalStorage: Send + Sync { /// Read a value by key. @@ -18,7 +19,7 @@ pub trait LocalStorage: Send + Sync { /// assert(result.isOk(), "read failed:", result); /// console.log("storage value read:", result.value.value); /// ``` - #[wire(request_id = 12)] + #[wire(request_id = 0)] async fn read( &self, cx: &CallContext, @@ -35,7 +36,7 @@ pub trait LocalStorage: Send + Sync { /// assert(result.isOk(), "write failed:", result); /// console.log("storage write succeeded"); /// ``` - #[wire(request_id = 14)] + #[wire(request_id = 2)] async fn write( &self, cx: &CallContext, @@ -49,7 +50,7 @@ pub trait LocalStorage: Send + Sync { /// assert(result.isOk(), "clear failed:", result); /// console.log("storage clear succeeded"); /// ``` - #[wire(request_id = 16)] + #[wire(request_id = 4)] async fn clear( &self, cx: &CallContext, diff --git a/rust/crates/truapi/src/api/notifications.rs b/rust/crates/truapi/src/api/notifications.rs index 01bf6769b..3857c28e0 100644 --- a/rust/crates/truapi/src/api/notifications.rs +++ b/rust/crates/truapi/src/api/notifications.rs @@ -5,10 +5,11 @@ use crate::versioned::notifications::{ HostPushNotificationCancelResponse, HostPushNotificationError, HostPushNotificationRequest, HostPushNotificationResponse, }; -use crate::wire; use crate::{CallContext, CallError}; +use crate::{wire, wire_trait}; /// Notification methods for locally-rendered push notifications. +#[wire_trait(id = 7)] #[crate::async_trait] pub trait Notifications: Send + Sync { /// Send a push notification to the user. @@ -28,7 +29,7 @@ pub trait Notifications: Send + Sync { /// assert(result.isOk(), "sendPushNotification failed:", result); /// console.log("notification sent:", result.value); /// ``` - #[wire(request_id = 4)] + #[wire(request_id = 0)] async fn send_push_notification( &self, cx: &CallContext, @@ -49,7 +50,7 @@ pub trait Notifications: Send + Sync { /// assert(result.isOk(), "cancelPushNotification failed:", result); /// console.log("notification cancelled"); /// ``` - #[wire(request_id = 134)] + #[wire(request_id = 2)] async fn cancel_push_notification( &self, cx: &CallContext, diff --git a/rust/crates/truapi/src/api/payment.rs b/rust/crates/truapi/src/api/payment.rs index 107825f7f..ce9a92dc4 100644 --- a/rust/crates/truapi/src/api/payment.rs +++ b/rust/crates/truapi/src/api/payment.rs @@ -7,10 +7,11 @@ use crate::versioned::payment::{ HostPaymentStatusSubscribeRequest, HostPaymentTopUpError, HostPaymentTopUpRequest, HostPaymentTopUpResponse, }; -use crate::wire; use crate::{CallContext, CallError, Subscription}; +use crate::{wire, wire_trait}; /// Payment request and balance/status subscription methods. +#[wire_trait(id = 8)] #[crate::async_trait] pub trait Payment: Send + Sync { /// Subscribe to payment balance updates. @@ -23,7 +24,7 @@ pub trait Payment: Send + Sync { /// ); /// console.log("balance received:", balance); /// ``` - #[wire(start_id = 118)] + #[wire(start_id = 0)] async fn balance_subscribe( &self, _cx: &CallContext, @@ -53,7 +54,7 @@ pub trait Payment: Send + Sync { /// assert(result.isOk(), "request failed:", result); /// console.log("payment requested:", result.value); /// ``` - #[wire(request_id = 124)] + #[wire(request_id = 6)] async fn request( &self, _cx: &CallContext, @@ -90,7 +91,7 @@ pub trait Payment: Send + Sync { /// ); /// console.log("payment status received:", status); /// ``` - #[wire(start_id = 126)] + #[wire(start_id = 8)] async fn status_subscribe( &self, _cx: &CallContext, @@ -112,7 +113,7 @@ pub trait Payment: Send + Sync { /// assert(result.isOk(), "topUp failed:", result); /// console.log("balance topped up"); /// ``` - #[wire(request_id = 122)] + #[wire(request_id = 4)] async fn top_up( &self, _cx: &CallContext, diff --git a/rust/crates/truapi/src/api/permissions.rs b/rust/crates/truapi/src/api/permissions.rs index a190984d9..d03c64101 100644 --- a/rust/crates/truapi/src/api/permissions.rs +++ b/rust/crates/truapi/src/api/permissions.rs @@ -4,10 +4,11 @@ use crate::versioned::permissions::{ HostDevicePermissionError, HostDevicePermissionRequest, HostDevicePermissionResponse, RemotePermissionError, RemotePermissionRequest, RemotePermissionResponse, }; -use crate::wire; use crate::{CallContext, CallError}; +use crate::{wire, wire_trait}; /// Permission request methods. +#[wire_trait(id = 9)] #[crate::async_trait] pub trait Permissions: Send + Sync { /// Request a device-capability permission from the user. @@ -17,7 +18,7 @@ pub trait Permissions: Send + Sync { /// assert(result.isOk(), "requestDevicePermission failed:", result); /// console.log("device permission result:", result.value); /// ``` - #[wire(request_id = 8)] + #[wire(request_id = 0)] async fn request_device_permission( &self, cx: &CallContext, @@ -33,7 +34,7 @@ pub trait Permissions: Send + Sync { /// assert(result.isOk(), "requestRemotePermission failed:", result); /// console.log("remote permission result:", result.value); /// ``` - #[wire(request_id = 10)] + #[wire(request_id = 2)] async fn request_remote_permission( &self, cx: &CallContext, diff --git a/rust/crates/truapi/src/api/preimage.rs b/rust/crates/truapi/src/api/preimage.rs index 5037f8cb3..9a6b4c567 100644 --- a/rust/crates/truapi/src/api/preimage.rs +++ b/rust/crates/truapi/src/api/preimage.rs @@ -4,10 +4,11 @@ use crate::versioned::preimage::{ RemotePreimageLookupSubscribeItem, RemotePreimageLookupSubscribeRequest, RemotePreimageSubmitError, RemotePreimageSubmitRequest, RemotePreimageSubmitResponse, }; -use crate::wire; use crate::{CallContext, CallError, Subscription}; +use crate::{wire, wire_trait}; /// Preimage lookup and submission methods. +#[wire_trait(id = 10)] #[crate::async_trait] pub trait Preimage: Send + Sync { /// Subscribe to preimage lookups for a given key. @@ -26,7 +27,7 @@ pub trait Preimage: Send + Sync { /// assert(item.value === value, "preimage lookup returned the wrong value:", item); /// console.log("preimage lookup received:", item); /// ``` - #[wire(start_id = 64)] + #[wire(start_id = 0)] async fn lookup_subscribe( &self, _cx: &CallContext, @@ -43,7 +44,7 @@ pub trait Preimage: Send + Sync { /// assert(result.isOk(), "submit failed:", result); /// console.log("preimage submitted:", result.value); /// ``` - #[wire(request_id = 68)] + #[wire(request_id = 4)] async fn submit( &self, _cx: &CallContext, diff --git a/rust/crates/truapi/src/api/resource_allocation.rs b/rust/crates/truapi/src/api/resource_allocation.rs index 02c72d0ca..234999c4a 100644 --- a/rust/crates/truapi/src/api/resource_allocation.rs +++ b/rust/crates/truapi/src/api/resource_allocation.rs @@ -4,10 +4,11 @@ use crate::versioned::resource_allocation::{ HostRequestResourceAllocationError, HostRequestResourceAllocationRequest, HostRequestResourceAllocationResponse, }; -use crate::wire; use crate::{CallContext, CallError}; +use crate::{wire, wire_trait}; /// Resource pre-allocation (allowance management). +#[wire_trait(id = 11)] #[crate::async_trait] pub trait ResourceAllocation: Send + Sync { /// Request the host to pre-allocate one or more resources. @@ -38,7 +39,7 @@ pub trait ResourceAllocation: Send + Sync { /// ); /// console.log("resource allocation outcomes:", result.value.outcomes); /// ``` - #[wire(request_id = 130)] + #[wire(request_id = 0)] async fn request( &self, _cx: &CallContext, diff --git a/rust/crates/truapi/src/api/signing.rs b/rust/crates/truapi/src/api/signing.rs index 273e848dc..30f009eca 100644 --- a/rust/crates/truapi/src/api/signing.rs +++ b/rust/crates/truapi/src/api/signing.rs @@ -12,10 +12,11 @@ use crate::versioned::signing::{ HostSignRawResponse, HostSignRawWithLegacyAccountError, HostSignRawWithLegacyAccountRequest, HostSignRawWithLegacyAccountResponse, }; -use crate::wire; use crate::{CallContext, CallError}; +use crate::{wire, wire_trait}; /// Signing operations. +#[wire_trait(id = 12)] #[crate::async_trait] pub trait Signing: Send + Sync { /// Construct a signed transaction for a product account. @@ -37,7 +38,7 @@ pub trait Signing: Send + Sync { /// assert(result.isOk(), "createTransaction failed:", result); /// console.log("transaction created:", result.value); /// ``` - #[wire(request_id = 30)] + #[wire(request_id = 0)] async fn create_transaction( &self, _cx: &CallContext, @@ -76,7 +77,7 @@ pub trait Signing: Send + Sync { /// assert(result.isOk(), "createTransactionWithLegacyAccount failed:", result); /// console.log("transaction created:", result.value); /// ``` - #[wire(request_id = 32)] + #[wire(request_id = 2)] async fn create_transaction_with_legacy_account( &self, _cx: &CallContext, @@ -104,7 +105,7 @@ pub trait Signing: Send + Sync { /// assert(result.isOk(), "signRawWithLegacyAccount failed:", result); /// console.log("raw bytes signed:", result.value); /// ``` - #[wire(request_id = 34)] + #[wire(request_id = 4)] async fn sign_raw_with_legacy_account( &self, _cx: &CallContext, @@ -146,7 +147,7 @@ pub trait Signing: Send + Sync { /// assert(result.isOk(), "signPayloadWithLegacyAccount failed:", result); /// console.log("payload signed:", result.value); /// ``` - #[wire(request_id = 36)] + #[wire(request_id = 6)] async fn sign_payload_with_legacy_account( &self, _cx: &CallContext, @@ -173,7 +174,7 @@ pub trait Signing: Send + Sync { /// assert(result.isOk(), "signRaw failed:", result); /// console.log("raw bytes signed:", result.value); /// ``` - #[wire(request_id = 114)] + #[wire(request_id = 8)] async fn sign_raw( &self, _cx: &CallContext, @@ -206,7 +207,7 @@ pub trait Signing: Send + Sync { /// assert(result.isOk(), "signPayload failed:", result); /// console.log("payload signed:", result.value); /// ``` - #[wire(request_id = 116)] + #[wire(request_id = 10)] async fn sign_payload( &self, _cx: &CallContext, diff --git a/rust/crates/truapi/src/api/statement_store.rs b/rust/crates/truapi/src/api/statement_store.rs index aa2415885..9abf1a697 100644 --- a/rust/crates/truapi/src/api/statement_store.rs +++ b/rust/crates/truapi/src/api/statement_store.rs @@ -9,10 +9,11 @@ use crate::versioned::statement_store::{ RemoteStatementStoreSubscribeError, RemoteStatementStoreSubscribeItem, RemoteStatementStoreSubscribeRequest, }; -use crate::wire; use crate::{CallContext, CallError, Subscription}; +use crate::{wire, wire_trait}; /// Statement store methods. +#[wire_trait(id = 13)] #[crate::async_trait] pub trait StatementStore: Send + Sync { /// Subscribe to statements matching a topic filter. @@ -52,7 +53,7 @@ pub trait StatementStore: Send + Sync { /// ); /// console.log("subscribe received", page); /// ``` - #[wire(start_id = 56)] + #[wire(start_id = 0)] async fn subscribe( &self, _cx: &CallContext, @@ -91,7 +92,7 @@ pub trait StatementStore: Send + Sync { /// console.log("proof created:", result.value); /// } /// ``` - #[wire(request_id = 60)] + #[wire(request_id = 4)] async fn create_proof( &self, _cx: &CallContext, @@ -118,7 +119,7 @@ pub trait StatementStore: Send + Sync { /// assert(result.isOk(), "createProof failed:", result); /// console.log("proof created:", result.value); /// ``` - #[wire(request_id = 132)] + #[wire(request_id = 8)] async fn create_proof_authorized( &self, _cx: &CallContext, @@ -150,7 +151,7 @@ pub trait StatementStore: Send + Sync { /// assert(result.isOk(), "submit failed:", result); /// console.log("statement submitted"); /// ``` - #[wire(request_id = 62)] + #[wire(request_id = 6)] async fn submit( &self, _cx: &CallContext, diff --git a/rust/crates/truapi/src/api/system.rs b/rust/crates/truapi/src/api/system.rs index b08c5da9c..5391503ec 100644 --- a/rust/crates/truapi/src/api/system.rs +++ b/rust/crates/truapi/src/api/system.rs @@ -5,11 +5,12 @@ use crate::versioned::system::{ HostHandshakeError, HostHandshakeRequest, HostHandshakeResponse, HostNavigateToError, HostNavigateToRequest, HostNavigateToResponse, }; -use crate::wire; use crate::{CallContext, CallError}; +use crate::{wire, wire_trait}; /// General-purpose TrUAPI methods for handshake, feature detection, /// and navigation. +#[wire_trait(id = 0)] #[crate::async_trait] pub trait System: Send + Sync { /// Negotiate the wire codec version with the product. @@ -26,7 +27,7 @@ pub trait System: Send + Sync { request: HostHandshakeRequest, ) -> Result> { let HostHandshakeRequest::V1(version) = request; - if version.codec_version == 1 { + if version.codec_version == 2 { Ok(HostHandshakeResponse::V1) } else { Err(CallError::Domain(HostHandshakeError::V1( @@ -65,7 +66,7 @@ pub trait System: Send + Sync { /// assert(result.isOk(), "navigateTo failed:", result); /// console.log("navigation succeeded"); /// ``` - #[wire(request_id = 6)] + #[wire(request_id = 4)] async fn navigate_to( &self, cx: &CallContext, diff --git a/rust/crates/truapi/src/api/theme.rs b/rust/crates/truapi/src/api/theme.rs index 7fcbad818..2d323f9db 100644 --- a/rust/crates/truapi/src/api/theme.rs +++ b/rust/crates/truapi/src/api/theme.rs @@ -1,10 +1,11 @@ //! Unified [`Theme`] trait. use crate::versioned::theme::HostThemeSubscribeItem; -use crate::wire; use crate::{CallContext, Subscription}; +use crate::{wire, wire_trait}; /// Host theme subscription. +#[wire_trait(id = 14)] #[crate::async_trait] pub trait Theme: Send + Sync { /// Subscribe to host theme changes. @@ -17,7 +18,7 @@ pub trait Theme: Send + Sync { /// ); /// console.log("theme received:", theme); /// ``` - #[wire(start_id = 104)] + #[wire(start_id = 0)] async fn subscribe(&self, _cx: &CallContext) -> Subscription { Subscription::empty() } diff --git a/rust/crates/truapi/src/lib.rs b/rust/crates/truapi/src/lib.rs index a138df037..c4b5d6c66 100644 --- a/rust/crates/truapi/src/lib.rs +++ b/rust/crates/truapi/src/lib.rs @@ -124,7 +124,7 @@ pub mod latest { pub type RemotePermissionResponse = LatestOf; } -pub use truapi_macros::wire; +pub use truapi_macros::{wire, wire_trait}; /// Per-message id carried from the transport frame. pub type RequestId = String; diff --git a/scripts/codegen.sh b/scripts/codegen.sh index 7dedd7eb4..4db6d56d2 100755 --- a/scripts/codegen.sh +++ b/scripts/codegen.sh @@ -12,7 +12,7 @@ # --platform-ts-output js/packages/truapi-host/src/generated # --platform-wasm-adapter-output js/packages/truapi-host/src/generated # --platform-rust-output rust/crates/truapi-server/src/wasm -# --codec-version 1 +# --codec-version 2 # # The client surface defaults to the latest wire version any versioned # wrapper exposes; pass `--client-version V` to pin to an older one. @@ -41,7 +41,7 @@ cargo run -p truapi-codegen -- \ --platform-wasm-adapter-output js/packages/truapi-host/src/generated \ --platform-rust-output rust/crates/truapi-server/src/wasm \ --explorer-output js/packages/truapi/src/explorer \ - --codec-version 1 + --codec-version 2 rustfmt +nightly --edition 2024 \ rust/crates/truapi-server/src/generated/dispatcher.rs \