From 89d09eecdaecb8cd4ebbc0a40452e6080578a9b8 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Thu, 23 Jul 2026 09:16:27 -0400 Subject: [PATCH 01/13] ql-fsm: restore pairing invite codec --- ql-fsm/src/pairing.rs | 17 ++++++++++++----- ql-fsm/src/tests/handshake.rs | 1 + ql-fsm/src/tests/mod.rs | 1 + ql-runtime/src/tests/handshake.rs | 2 ++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ql-fsm/src/pairing.rs b/ql-fsm/src/pairing.rs index fb9b0532..12eec3e8 100644 --- a/ql-fsm/src/pairing.rs +++ b/ql-fsm/src/pairing.rs @@ -1,9 +1,16 @@ use ql_common::QID; use ql_wire::PairingToken; -/// Out-of-band invite consumed by the initiator of an XX pairing -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PairingInvite { - pub qid: QID, - pub token: PairingToken, +ql_codec::codec! { + /// Out-of-band invite consumed by the initiator of an XX pairing + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct PairingInvite { + pub version: u8, + pub qid: QID, + pub token: PairingToken, + } +} + +impl PairingInvite { + pub const VERSION: u8 = 1; } diff --git a/ql-fsm/src/tests/handshake.rs b/ql-fsm/src/tests/handshake.rs index b517dcc1..3ffe083a 100644 --- a/ql-fsm/src/tests/handshake.rs +++ b/ql-fsm/src/tests/handshake.rs @@ -106,6 +106,7 @@ fn connect_methods_require_bound_peer() { fsm.connect_xx( time, PairingInvite { + version: PairingInvite::VERSION, qid: ql_common::QID([2; ql_common::QID::SIZE]), token: pairing_token(2), }, diff --git a/ql-fsm/src/tests/mod.rs b/ql-fsm/src/tests/mod.rs index d7003faa..60986ae3 100644 --- a/ql-fsm/src/tests/mod.rs +++ b/ql-fsm/src/tests/mod.rs @@ -203,6 +203,7 @@ impl Harness { fsm.connect_xx( time, PairingInvite { + version: PairingInvite::VERSION, qid: remote_qid, token, }, diff --git a/ql-runtime/src/tests/handshake.rs b/ql-runtime/src/tests/handshake.rs index fb915d4d..805371e0 100644 --- a/ql-runtime/src/tests/handshake.rs +++ b/ql-runtime/src/tests/handshake.rs @@ -139,6 +139,7 @@ async fn start_pairing_round_trip_connects_when_armed() { handle_b.arm_pairing(token); handle_a.start_pairing(PairingInvite { + version: PairingInvite::VERSION, qid: identity_b.qid, token, }); @@ -168,6 +169,7 @@ async fn start_pairing_does_not_connect_when_unarmed() { spawn_forwarder(outbound_b, inbound_a_tx); handle_a.start_pairing(PairingInvite { + version: PairingInvite::VERSION, qid: identity_b.qid, token, }); From ace30bbabdd25fa91554795626eb88c51f11cf1e Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 5 May 2026 09:16:40 -0400 Subject: [PATCH 02/13] ql-api: init --- Cargo.lock | 383 +++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + ql-api/Cargo.toml | 20 +++ ql-api/src/app_store.rs | 73 ++++++++ ql-api/src/benchmark.rs | 72 ++++++++ ql-api/src/bootstrap.rs | 22 +++ ql-api/src/codec.rs | 90 ++++++++++ ql-api/src/lib.rs | 38 ++++ 8 files changed, 694 insertions(+), 5 deletions(-) create mode 100644 ql-api/Cargo.toml create mode 100644 ql-api/src/app_store.rs create mode 100644 ql-api/src/benchmark.rs create mode 100644 ql-api/src/bootstrap.rs create mode 100644 ql-api/src/codec.rs create mode 100644 ql-api/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 38355b76..1e4bacb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,12 +26,40 @@ dependencies = [ "memchr", ] +[[package]] +name = "allo-isolate" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449e356a4864c017286dbbec0e12767ea07efba29e3b7d984194c2a7ff3c4550" +dependencies = [ + "anyhow", + "atomic", + "backtrace", +] + [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" +dependencies = [ + "android_log-sys", + "env_filter 0.1.4", + "log", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -91,6 +119,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "async-channel" version = "2.5.0" @@ -103,6 +137,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + [[package]] name = "autocfg" version = "1.5.0" @@ -175,6 +215,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "build-target" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b" + [[package]] name = "bumpalo" version = "3.19.0" @@ -224,6 +270,12 @@ dependencies = [ "syn", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -259,6 +311,33 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "colorchoice" version = "1.0.5" @@ -287,6 +366,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "consts" version = "1.0.0" @@ -340,6 +429,28 @@ dependencies = [ "typenum", ] +[[package]] +name = "dart-sys" +version = "4.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57967e4b200d767d091b961d6ab42cc7d0cc14fe9e052e75d0d3cf9eb732d895" +dependencies = [ + "cc", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "dcbor" version = "0.23.3" @@ -354,6 +465,17 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "delegate-attr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "diatomic-waker" version = "0.2.3" @@ -376,6 +498,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_filter" version = "1.0.1" @@ -394,7 +526,7 @@ checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "anstream", "anstyle", - "env_filter", + "env_filter 1.0.1", "jiff", "log", ] @@ -443,23 +575,101 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "flutter_rust_bridge" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde126295b2acc5f0a712e265e91b6fdc0ed38767496483e592ae7134db83725" +dependencies = [ + "allo-isolate", + "android_logger", + "anyhow", + "build-target", + "bytemuck", + "byteorder", + "console_error_panic_hook", + "dart-sys", + "delegate-attr", + "flutter_rust_bridge_macros", + "futures", + "js-sys", + "lazy_static", + "log", + "oslog", + "portable-atomic", + "threadpool", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "flutter_rust_bridge_macros" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f0420326b13675321b194928bb7830043b68cf8b810e1c651285c747abb080" +dependencies = [ + "hex", + "md-5", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -474,6 +684,46 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + [[package]] name = "generator" version = "0.8.8" @@ -538,6 +788,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -587,6 +843,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -791,6 +1053,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -819,6 +1090,16 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.5" @@ -902,6 +1183,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.36.7" @@ -929,6 +1220,17 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea" +[[package]] +name = "oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" +dependencies = [ + "cc", + "dashmap", + "log", +] + [[package]] name = "parking" version = "2.2.1" @@ -938,6 +1240,19 @@ dependencies = [ "loom", ] +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + [[package]] name = "paste" version = "1.0.15" @@ -1050,6 +1365,17 @@ dependencies = [ "syn", ] +[[package]] +name = "ql-api" +version = "0.1.0" +dependencies = [ + "bytes", + "ciborium", + "flutter_rust_bridge", + "ql-rpc", + "serde", +] + [[package]] name = "ql-codec" version = "0.1.0" @@ -1188,6 +1514,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.12.3" @@ -1305,6 +1640,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.228" @@ -1450,6 +1791,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "tinyvec" version = "1.10.0" @@ -1678,6 +2028,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -1710,6 +2073,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "windows-core" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index 056fd430..a61e9d98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "btp", "ql-codec", "ql-common", + "ql-api", "ql-fsm", "ql-rpc", "ql-runtime", diff --git a/ql-api/Cargo.toml b/ql-api/Cargo.toml new file mode 100644 index 00000000..9188a3a8 --- /dev/null +++ b/ql-api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "ql-api" +version = "0.1.0" +edition = "2021" +description = "KeyOS QLv2 RPC protocol" +license = "Proprietary" + + +[dependencies] +ql-rpc = { workspace = true } +bytes = { workspace = true } +ciborium = "0.2.2" +flutter_rust_bridge = { version = "=2.11.1", optional = true } +serde = { version = "1.0", features = ["derive"] } + +[features] +frb = ["flutter_rust_bridge"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] } diff --git a/ql-api/src/app_store.rs b/ql-api/src/app_store.rs new file mode 100644 index 00000000..92faf038 --- /dev/null +++ b/ql-api/src/app_store.rs @@ -0,0 +1,73 @@ +use ql_rpc::{Download, Request, RouteId}; + +use crate::{codec::rpc, Error, Route}; + +rpc! { + #[derive(Copy)] + pub struct AppVersion(pub u32, pub u32, pub u32); +} + +// +// LIST APPS +// + +pub struct ListApps; + +rpc! { + pub struct ListAppsRequest { + // + } +} + +rpc! { + pub struct ListAppsResponse { + pub apps: Vec, + } +} + +rpc! { + pub struct AppDetails { + pub name: String, + pub icon: String, // SVG? + pub developer_id: Vec, + pub app_id: Vec, + } +} + +impl Request for ListApps { + const ROUTE: RouteId = Route::ListApps.id(); + type Error = Error; + type Request = ListAppsRequest; + type Response = ListAppsResponse; +} + +// +// APP DOWNLOAD +// +// download a single tar file that contains: +// - app.elf +// - manifest.json + +pub struct AppDownload; + +rpc! { + pub struct AppDownloadRequest { + pub version: Option, + } +} + +rpc! { + pub struct AppDownloadHeader { + pub version: String, + } +} + +impl Download for AppDownload { + const ROUTE: RouteId = Route::AppDownload.id(); + + type Error = Error; + + type Request = AppDownloadRequest; + + type ResponseHeader = AppDownloadHeader; +} diff --git a/ql-api/src/benchmark.rs b/ql-api/src/benchmark.rs new file mode 100644 index 00000000..0b6504d4 --- /dev/null +++ b/ql-api/src/benchmark.rs @@ -0,0 +1,72 @@ +use ql_rpc::{request::Request, Download, RouteId, Subscription}; + +use crate::{codec::rpc, Error, Route}; + +// Echo request-response benchmark +pub struct Echo; + +impl Request for Echo { + type Error = Error; + type Request = EchoRequest; + type Response = EchoResponse; + + const ROUTE: RouteId = Route::Echo.id(); +} + +rpc! { + pub struct EchoRequest { + pub message: String, + } +} + +rpc! { + pub struct EchoResponse { + pub message: String, + } +} + +// Byte-stream subscription benchmark +pub struct BytesBenchmark; + +impl Subscription for BytesBenchmark { + type Error = Error; + type Event = BenchmarkEvent; + type Request = BenchmarkRequest; + + const ROUTE: ql_rpc::RouteId = Route::BytesBenchmark.id(); +} + +rpc! { + pub struct BenchmarkRequest { + pub length: u32, + } +} + +rpc! { + pub struct BenchmarkEvent { + pub bytes: Vec, + } +} + +// Raw-body download benchmark +pub struct DownloadBenchmark; + +impl Download for DownloadBenchmark { + type Error = Error; + type Request = DownloadBenchmarkRequest; + type ResponseHeader = DownloadBenchmarkHeader; + + const ROUTE: RouteId = Route::DownloadBenchmark.id(); +} + +rpc! { + pub struct DownloadBenchmarkRequest { + pub length: u64, + } +} + +rpc! { + pub struct DownloadBenchmarkHeader { + pub hash: Vec, + } +} diff --git a/ql-api/src/bootstrap.rs b/ql-api/src/bootstrap.rs new file mode 100644 index 00000000..9f050d4b --- /dev/null +++ b/ql-api/src/bootstrap.rs @@ -0,0 +1,22 @@ +use crate::{codec::rpc, Error, Route}; + +pub struct BootstrapQlv1; + +impl ql_rpc::Request for BootstrapQlv1 { + const ROUTE: ql_rpc::RouteId = Route::BootstrapQlv1.id(); + type Error = Error; + type Request = BootstrapRequest; + type Response = BootstrapResponse; +} + +rpc! { + pub struct BootstrapRequest { + pub xid_document: Vec, + } +} + +rpc! { + pub struct BootstrapResponse { + pub xid_document: Vec, + } +} diff --git a/ql-api/src/codec.rs b/ql-api/src/codec.rs new file mode 100644 index 00000000..7d5323f2 --- /dev/null +++ b/ql-api/src/codec.rs @@ -0,0 +1,90 @@ +use std::io; + +use bytes::{Buf, BufMut}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::Error; + +macro_rules! impl_codec { + ($ty:ty) => { + impl ql_rpc::RpcCodec for $ty { + type Error = crate::Error; + + fn encode_value(&self, out: &mut B) { + $crate::codec::encode_cbor(self, out); + } + + fn decode_value(bytes: &mut B) -> Result { + $crate::codec::decode_cbor(bytes) + } + } + }; +} + +pub(crate) use impl_codec; + +macro_rules! rpc { + ($(#[$attr:meta])* $vis:vis struct $name:ident;) => { + compile_error!("rpc! does not support unit structs"); + }; + ($(#[$attr:meta])* $vis:vis struct $name:ident $($body:tt)+) => { + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + $(#[$attr])* + #[cfg_attr(feature = "frb", flutter_rust_bridge::frb(non_opaque))] + $vis struct $name $($body)+ + + $crate::codec::impl_codec!($name); + }; +} + +pub(crate) use rpc; + +pub fn encode_cbor(value: &T, out: &mut B) +where + T: Serialize, + B: BufMut + ?Sized, +{ + ciborium::ser::into_writer(value, BufMutWriter(out)) + .expect("CBOR serialization to BufMut failed"); +} + +pub fn decode_cbor(bytes: &mut B) -> Result +where + T: DeserializeOwned, + B: Buf, +{ + let value = ciborium::de::from_reader(BufReader(bytes))?; + bytes.advance(bytes.remaining()); + Ok(value) +} + +struct BufMutWriter<'a, B: BufMut + ?Sized>(&'a mut B); + +impl io::Write for BufMutWriter<'_, B> { + fn write(&mut self, data: &[u8]) -> io::Result { + if self.0.remaining_mut() < data.len() { + return Err(io::ErrorKind::WriteZero.into()); + } + + self.0.put_slice(data); + Ok(data.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +struct BufReader<'a, B: Buf>(&'a mut B); + +impl io::Read for BufReader<'_, B> { + fn read(&mut self, out: &mut [u8]) -> io::Result { + let len = out.len().min(self.0.remaining()); + if len == 0 { + return Ok(0); + } + + self.0.copy_to_slice(&mut out[..len]); + Ok(len) + } +} diff --git a/ql-api/src/lib.rs b/ql-api/src/lib.rs new file mode 100644 index 00000000..3899a2f7 --- /dev/null +++ b/ql-api/src/lib.rs @@ -0,0 +1,38 @@ +// 1. list catalog of apps (request/response) +// 2. get app by id +// 3. download version? + +mod codec; +mod app_store; +mod benchmark; +mod bootstrap; + +pub use app_store::*; +pub use benchmark::*; +pub use bootstrap::*; + +pub type Error = ciborium::de::Error; + +pub const SERVICE_ID: ql_rpc::ServiceId = ql_rpc::ServiceId([0; 16]); + +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Route { + // setup + BootstrapQlv1 = 100, + + // app store + ListApps = 200, + AppDownload = 201, + + // debug + Echo = 1000, + BytesBenchmark = 1001, + DownloadBenchmark = 1002, +} + +impl Route { + pub const fn id(self) -> ql_rpc::RouteId { + ql_rpc::RouteId::from_u32(self as u32) + } +} From e04456b4074eda819f37125d797e0b1165f0ab73 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Thu, 14 May 2026 09:28:33 -0400 Subject: [PATCH 03/13] ql-api: migrate all legacy apis --- Cargo.lock | 1 + ql-api/Cargo.toml | 1 + ql-api/src/app_store.rs | 19 +++-- ql-api/src/backup.rs | 167 +++++++++++++++++++++++++++++++++++++++ ql-api/src/benchmark.rs | 27 +++---- ql-api/src/bitcoin.rs | 78 ++++++++++++++++++ ql-api/src/bootstrap.rs | 22 ------ ql-api/src/codec.rs | 34 -------- ql-api/src/firmware.rs | 87 ++++++++++++++++++++ ql-api/src/fx.rs | 84 ++++++++++++++++++++ ql-api/src/lib.rs | 80 +++++++++++++++---- ql-api/src/macros.rs | 82 +++++++++++++++++++ ql-api/src/onboarding.rs | 30 +++++++ ql-api/src/scv.rs | 44 +++++++++++ ql-api/src/status.rs | 52 ++++++++++++ ql-api/src/timezone.rs | 20 +++++ 16 files changed, 729 insertions(+), 99 deletions(-) create mode 100644 ql-api/src/backup.rs create mode 100644 ql-api/src/bitcoin.rs delete mode 100644 ql-api/src/bootstrap.rs create mode 100644 ql-api/src/firmware.rs create mode 100644 ql-api/src/fx.rs create mode 100644 ql-api/src/macros.rs create mode 100644 ql-api/src/onboarding.rs create mode 100644 ql-api/src/scv.rs create mode 100644 ql-api/src/status.rs create mode 100644 ql-api/src/timezone.rs diff --git a/Cargo.lock b/Cargo.lock index 1e4bacb1..33639331 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1372,6 +1372,7 @@ dependencies = [ "bytes", "ciborium", "flutter_rust_bridge", + "ql-common", "ql-rpc", "serde", ] diff --git a/ql-api/Cargo.toml b/ql-api/Cargo.toml index 9188a3a8..c81fbefb 100644 --- a/ql-api/Cargo.toml +++ b/ql-api/Cargo.toml @@ -7,6 +7,7 @@ license = "Proprietary" [dependencies] +ql-common = { workspace = true } ql-rpc = { workspace = true } bytes = { workspace = true } ciborium = "0.2.2" diff --git a/ql-api/src/app_store.rs b/ql-api/src/app_store.rs index 92faf038..629b6a80 100644 --- a/ql-api/src/app_store.rs +++ b/ql-api/src/app_store.rs @@ -1,6 +1,6 @@ -use ql_rpc::{Download, Request, RouteId}; +use ql_rpc::{Download, Request}; -use crate::{codec::rpc, Error, Route}; +use crate::{route, Error}; rpc! { #[derive(Copy)] @@ -11,8 +11,6 @@ rpc! { // LIST APPS // -pub struct ListApps; - rpc! { pub struct ListAppsRequest { // @@ -34,8 +32,7 @@ rpc! { } } -impl Request for ListApps { - const ROUTE: RouteId = Route::ListApps.id(); +impl Request for route::ListApps { type Error = Error; type Request = ListAppsRequest; type Response = ListAppsResponse; @@ -48,8 +45,6 @@ impl Request for ListApps { // - app.elf // - manifest.json -pub struct AppDownload; - rpc! { pub struct AppDownloadRequest { pub version: Option, @@ -62,12 +57,16 @@ rpc! { } } -impl Download for AppDownload { - const ROUTE: RouteId = Route::AppDownload.id(); +rpc! { + pub struct AppDownloadPartHeader {} +} +impl Download for route::AppDownload { type Error = Error; type Request = AppDownloadRequest; type ResponseHeader = AppDownloadHeader; + + type PartHeader = AppDownloadPartHeader; } diff --git a/ql-api/src/backup.rs b/ql-api/src/backup.rs new file mode 100644 index 00000000..e2c32a46 --- /dev/null +++ b/ql-api/src/backup.rs @@ -0,0 +1,167 @@ +use ql_rpc::{Download, Notification, Request, Upload}; + +use crate::{route, Error}; + +rpc! { + pub struct Shard(pub Vec); +} + +rpc! { + pub struct SeedFingerprint(pub [u8; 32]); +} + +rpc! { + pub struct BackupShardRequest { + pub shard: Shard, + } +} + +rpc! { + pub enum BackupShardResponse { + Success, + Error { error: String }, + } +} + +impl Request for route::BackupShard { + type Error = Error; + type Request = BackupShardRequest; + type Response = BackupShardResponse; +} + +rpc! { + pub struct RestoreShardRequest { + pub seed_fingerprint: SeedFingerprint, + pub timestamp: Option, + } +} + +rpc! { + pub enum RestoreShardResponse { + Success { shard: Shard }, + Error { error: String }, + NotFound, + } +} + +impl Request for route::RestoreShard { + type Error = Error; + type Request = RestoreShardRequest; + type Response = RestoreShardResponse; +} + +rpc! { + pub struct EnvoyMagicBackupEnabledRequest {} +} + +rpc! { + pub struct EnvoyMagicBackupEnabledResponse { + pub enabled: bool, + } +} + +impl Request for route::EnvoyMagicBackupEnabled { + type Error = Error; + type Request = EnvoyMagicBackupEnabledRequest; + type Response = EnvoyMagicBackupEnabledResponse; +} + +rpc! { + pub struct PassportMagicBackupEnabledPayload { + pub enabled: bool, + pub seed_fingerprint: SeedFingerprint, + } +} + +impl Notification for route::PassportMagicBackupEnabled { + type Error = Error; + type Payload = PassportMagicBackupEnabledPayload; +} + +rpc! { + pub struct PassportMagicBackupStatusRequest { + pub seed_fingerprint: SeedFingerprint, + pub timestamp: Option, + } +} + +rpc! { + pub struct PassportMagicBackupStatusResponse { + pub shard_backup_found: bool, + } +} + +impl Request for route::PassportMagicBackupStatus { + type Error = Error; + type Request = PassportMagicBackupStatusRequest; + type Response = PassportMagicBackupStatusResponse; +} + +rpc! { + pub struct UploadMagicBackupRequest { + pub seed_fingerprint: SeedFingerprint, + pub total_size: Option, + pub hash: [u8; 32], + } +} + +rpc! { + pub enum UploadMagicBackupResult { + Success, + Error { error: String }, + } +} + +rpc! { + pub struct UploadMagicBackupPartHeader {} +} + +impl Upload for route::UploadMagicBackup { + type Error = Error; + type Request = UploadMagicBackupRequest; + type PartHeader = UploadMagicBackupPartHeader; + type Response = UploadMagicBackupResult; +} + +rpc! { + pub struct DownloadMagicBackupRequest { + pub seed_fingerprint: SeedFingerprint, + } +} + +rpc! { + pub enum DownloadMagicBackupHeader { + Found(BackupMetadata), + NotFound, + Error { error: String }, + } +} + +rpc! { + pub struct BackupMetadata { + pub total_size: Option, + } +} + +rpc! { + pub struct DownloadMagicBackupPartHeader {} +} + +impl Download for route::DownloadMagicBackup { + type Error = Error; + type Request = DownloadMagicBackupRequest; + type ResponseHeader = DownloadMagicBackupHeader; + type PartHeader = DownloadMagicBackupPartHeader; +} + +rpc! { + pub enum RestoreMagicBackupResult { + Success, + Error { error: String }, + } +} + +impl Notification for route::RestoreMagicBackupComplete { + type Error = Error; + type Payload = RestoreMagicBackupResult; +} diff --git a/ql-api/src/benchmark.rs b/ql-api/src/benchmark.rs index 0b6504d4..3409899c 100644 --- a/ql-api/src/benchmark.rs +++ b/ql-api/src/benchmark.rs @@ -1,16 +1,12 @@ -use ql_rpc::{request::Request, Download, RouteId, Subscription}; +use ql_rpc::{request::Request, Download, Subscription}; -use crate::{codec::rpc, Error, Route}; +use crate::{route, Error}; // Echo request-response benchmark -pub struct Echo; - -impl Request for Echo { +impl Request for route::Echo { type Error = Error; type Request = EchoRequest; type Response = EchoResponse; - - const ROUTE: RouteId = Route::Echo.id(); } rpc! { @@ -26,14 +22,10 @@ rpc! { } // Byte-stream subscription benchmark -pub struct BytesBenchmark; - -impl Subscription for BytesBenchmark { +impl Subscription for route::BytesBenchmark { type Error = Error; type Event = BenchmarkEvent; type Request = BenchmarkRequest; - - const ROUTE: ql_rpc::RouteId = Route::BytesBenchmark.id(); } rpc! { @@ -49,14 +41,11 @@ rpc! { } // Raw-body download benchmark -pub struct DownloadBenchmark; - -impl Download for DownloadBenchmark { +impl Download for route::DownloadBenchmark { type Error = Error; type Request = DownloadBenchmarkRequest; type ResponseHeader = DownloadBenchmarkHeader; - - const ROUTE: RouteId = Route::DownloadBenchmark.id(); + type PartHeader = DownloadBenchmarkPartHeader; } rpc! { @@ -70,3 +59,7 @@ rpc! { pub hash: Vec, } } + +rpc! { + pub struct DownloadBenchmarkPartHeader {} +} diff --git a/ql-api/src/bitcoin.rs b/ql-api/src/bitcoin.rs new file mode 100644 index 00000000..9235247d --- /dev/null +++ b/ql-api/src/bitcoin.rs @@ -0,0 +1,78 @@ +use ql_rpc::{Notification, Request}; + +use crate::{route, Error}; + +rpc! { + pub struct SignPsbtRequest { + pub account_id: String, + pub psbt: Vec, + } +} + +rpc! { + pub enum SignPsbtResponse { + Signed { psbt: Vec }, + Rejected, + Error { error: String }, + } +} + +impl Request for route::SignPsbt { + type Error = Error; + type Request = SignPsbtRequest; + type Response = SignPsbtResponse; +} + +rpc! { + pub struct BroadcastTransactionRequest { + pub account_id: String, + pub psbt: Vec, + } +} + +rpc! { + pub enum BroadcastTransactionResponse { + Broadcast { txid: String }, + Error { error: String }, + } +} + +impl Request for route::BroadcastTransaction { + type Error = Error; + type Request = BroadcastTransactionRequest; + type Response = BroadcastTransactionResponse; +} + +rpc! { + pub struct AccountUpdatePayload { + pub account_id: String, + pub update: Vec, + } +} + +impl Notification for route::AccountUpdate { + type Error = Error; + type Payload = AccountUpdatePayload; +} + +rpc! { + pub struct ActiveSeedFingerprint { + pub fingerprint: String, + pub has_passphrase: bool, + } +} + +impl Notification for route::PassportActiveSeedFingerprint { + type Error = Error; + type Payload = ActiveSeedFingerprint; +} + +rpc! { + pub struct PassportActiveSeedFingerprintRequest {} +} + +impl Request for route::GetPassportActiveSeedFingerprint { + type Error = Error; + type Request = PassportActiveSeedFingerprintRequest; + type Response = ActiveSeedFingerprint; +} diff --git a/ql-api/src/bootstrap.rs b/ql-api/src/bootstrap.rs deleted file mode 100644 index 9f050d4b..00000000 --- a/ql-api/src/bootstrap.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::{codec::rpc, Error, Route}; - -pub struct BootstrapQlv1; - -impl ql_rpc::Request for BootstrapQlv1 { - const ROUTE: ql_rpc::RouteId = Route::BootstrapQlv1.id(); - type Error = Error; - type Request = BootstrapRequest; - type Response = BootstrapResponse; -} - -rpc! { - pub struct BootstrapRequest { - pub xid_document: Vec, - } -} - -rpc! { - pub struct BootstrapResponse { - pub xid_document: Vec, - } -} diff --git a/ql-api/src/codec.rs b/ql-api/src/codec.rs index 7d5323f2..eda25685 100644 --- a/ql-api/src/codec.rs +++ b/ql-api/src/codec.rs @@ -5,40 +5,6 @@ use serde::{de::DeserializeOwned, Serialize}; use crate::Error; -macro_rules! impl_codec { - ($ty:ty) => { - impl ql_rpc::RpcCodec for $ty { - type Error = crate::Error; - - fn encode_value(&self, out: &mut B) { - $crate::codec::encode_cbor(self, out); - } - - fn decode_value(bytes: &mut B) -> Result { - $crate::codec::decode_cbor(bytes) - } - } - }; -} - -pub(crate) use impl_codec; - -macro_rules! rpc { - ($(#[$attr:meta])* $vis:vis struct $name:ident;) => { - compile_error!("rpc! does not support unit structs"); - }; - ($(#[$attr:meta])* $vis:vis struct $name:ident $($body:tt)+) => { - #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] - $(#[$attr])* - #[cfg_attr(feature = "frb", flutter_rust_bridge::frb(non_opaque))] - $vis struct $name $($body)+ - - $crate::codec::impl_codec!($name); - }; -} - -pub(crate) use rpc; - pub fn encode_cbor(value: &T, out: &mut B) where T: Serialize, diff --git a/ql-api/src/firmware.rs b/ql-api/src/firmware.rs new file mode 100644 index 00000000..bf0b49e0 --- /dev/null +++ b/ql-api/src/firmware.rs @@ -0,0 +1,87 @@ +use ql_rpc::{Download, Notification, Request}; + +use crate::{route, Error}; + +rpc! { + pub struct FirmwareUpdateCheckRequest { + pub current_version: String, + } +} + +rpc! { + pub enum FirmwareUpdateCheckResponse { + Available(FirmwareUpdateAvailable), + NotAvailable, + } +} + +rpc! { + pub struct FirmwareUpdateAvailable { + pub version: String, + pub changelog: String, + pub timestamp: u32, + pub total_size: u32, + pub patch_count: u8, + } +} + +impl Request for route::FirmwareUpdateCheck { + type Error = Error; + type Request = FirmwareUpdateCheckRequest; + type Response = FirmwareUpdateCheckResponse; +} + +rpc! { + pub struct FirmwareDownloadRequest { + pub current_version: String, + } +} + +rpc! { + pub enum FirmwareDownloadHeader { + Available(FirmwareUpdateAvailable), + NotAvailable, + Error { error: String }, + } +} + +rpc! { + pub struct FirmwareDownloadPartHeader { + pub patch_name: String, + pub size_bytes: u64, + } +} + +impl Download for route::FirmwareDownload { + type Error = Error; + type Request = FirmwareDownloadRequest; + type ResponseHeader = FirmwareDownloadHeader; + type PartHeader = FirmwareDownloadPartHeader; +} + +rpc! { + pub enum FirmwareInstallEvent { + Installing, + Rebooting, + Success { + installed_version: String, + }, + Error { + error: String, + stage: InstallErrorStage, + }, + } +} + +rpc! { + pub enum InstallErrorStage { + Download, + Verify, + Install, + } +} + +impl Notification for route::FirmwareInstallStatus { + type Error = Error; + type Payload = FirmwareInstallEvent; +} diff --git a/ql-api/src/fx.rs b/ql-api/src/fx.rs new file mode 100644 index 00000000..260cf3d4 --- /dev/null +++ b/ql-api/src/fx.rs @@ -0,0 +1,84 @@ +use ql_rpc::{Notification, Request, Subscription}; + +use crate::{route, Error}; + +rpc! { + #[PartialEq] + pub struct ExchangeRate { + pub currency_code: String, + pub rate: f32, + pub timestamp: u64, + } +} + +rpc! { + #[PartialEq] + pub struct PricePoint { + pub rate: f32, + pub timestamp: u64, + } +} + +rpc! { + pub struct ExchangeRateHistoryRequest { + pub currency_code: String, + } +} + +rpc! { + #[PartialEq] + pub struct ExchangeRateHistory { + pub history: Vec, + pub currency_code: String, + } +} + +impl Notification for route::ExchangeRateUpdate { + type Error = Error; + type Payload = ExchangeRate; +} + +rpc! { + pub struct ExchangeRateSubscriptionRequest { + pub currency_code: String, + } +} + +impl Subscription for route::ExchangeRateSubscription { + type Error = Error; + type Request = ExchangeRateSubscriptionRequest; + type Event = ExchangeRate; +} + +impl Request for route::ExchangeRateHistory { + type Error = Error; + type Request = ExchangeRateHistoryRequest; + type Response = ExchangeRateHistory; +} + +rpc! { + pub struct PassportFiatPreferencePayload { + pub currency_code: String, + } +} + +impl Notification for route::PassportFiatPreference { + type Error = Error; + type Payload = PassportFiatPreferencePayload; +} + +rpc! { + pub struct PassportFiatPreferenceRequest {} +} + +rpc! { + pub struct PassportFiatPreferenceResponse { + pub currency_code: String, + } +} + +impl Request for route::PassportFiatPreferenceRequest { + type Error = Error; + type Request = PassportFiatPreferenceRequest; + type Response = PassportFiatPreferenceResponse; +} diff --git a/ql-api/src/lib.rs b/ql-api/src/lib.rs index 3899a2f7..2f694dd8 100644 --- a/ql-api/src/lib.rs +++ b/ql-api/src/lib.rs @@ -2,37 +2,85 @@ // 2. get app by id // 3. download version? -mod codec; +#[macro_use] +mod macros; + mod app_store; +mod backup; mod benchmark; -mod bootstrap; +mod bitcoin; +mod codec; +mod firmware; +mod fx; +mod onboarding; +mod scv; +mod status; +mod timezone; pub use app_store::*; +pub use backup::*; pub use benchmark::*; -pub use bootstrap::*; +pub use bitcoin::*; +pub use firmware::*; +pub use fx::*; +pub use onboarding::*; +pub use scv::*; +pub use status::*; +pub use timezone::*; pub type Error = ciborium::de::Error; -pub const SERVICE_ID: ql_rpc::ServiceId = ql_rpc::ServiceId([0; 16]); - -#[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Route { - // setup - BootstrapQlv1 = 100, +pub const SERVICE_ID: ql_common::ServiceId = ql_common::ServiceId([0; 16]); +routes! { // app store ListApps = 200, AppDownload = 201, + // status and presence + DeviceStatus = 300, + EnvoyStatus = 301, + Timezone = 303, + + // status and presence + PassportDetails = 402, + PassportDetailsUpdated = 403, + + // security and onboarding + SecurityCheck = 500, + OnboardingStatus = 501, + + // firmware + FirmwareUpdateCheck = 600, + FirmwareDownload = 601, + FirmwareInstallStatus = 602, + + // market data + ExchangeRateUpdate = 700, + ExchangeRateSubscription = 701, + ExchangeRateHistory = 702, + PassportFiatPreference = 703, + PassportFiatPreferenceRequest = 704, + + // bitcoin and wallet + SignPsbt = 800, + BroadcastTransaction = 801, + AccountUpdate = 802, + PassportActiveSeedFingerprint = 803, + GetPassportActiveSeedFingerprint = 804, + + // backup + BackupShard = 900, + RestoreShard = 901, + EnvoyMagicBackupEnabled = 902, + PassportMagicBackupEnabled = 903, + PassportMagicBackupStatus = 904, + UploadMagicBackup = 905, + DownloadMagicBackup = 906, + RestoreMagicBackupComplete = 907, + // debug Echo = 1000, BytesBenchmark = 1001, DownloadBenchmark = 1002, } - -impl Route { - pub const fn id(self) -> ql_rpc::RouteId { - ql_rpc::RouteId::from_u32(self as u32) - } -} diff --git a/ql-api/src/macros.rs b/ql-api/src/macros.rs new file mode 100644 index 00000000..42d0a1f2 --- /dev/null +++ b/ql-api/src/macros.rs @@ -0,0 +1,82 @@ +macro_rules! impl_codec { + ($ty:ty) => { + impl ql_rpc::RpcCodec for $ty { + type Error = crate::Error; + + fn encode_value(&self, out: &mut B) { + $crate::codec::encode_cbor(self, out); + } + + fn decode_value( + bytes: &mut B, + ) -> Result::Error> { + $crate::codec::decode_cbor(bytes) + } + } + }; +} + +macro_rules! routes { + ($($route:ident = $id:literal,)*) => { + #[repr(u32)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum Route { + $($route = $id,)* + } + + impl Route { + pub const fn id(self) -> ql_common::RouteId { + ql_common::RouteId::from_u32(self as u32) + } + } + + pub mod route { + $( + #[derive(Debug)] + pub struct $route; + + impl ql_rpc::Route for $route { + const SERVICE: ql_common::ServiceId = crate::SERVICE_ID; + const ROUTE: ql_common::RouteId = super::Route::$route.id(); + } + )* + } + }; +} + +macro_rules! rpc { + ($(#[$attr:meta])* $vis:vis struct $name:ident;) => { + compile_error!("rpc! does not support unit structs"); + }; + (#[PartialEq] $(#[$attr:meta])* $vis:vis struct $name:ident $($body:tt)+) => { + rpc_item!(PartialEq; $(#[$attr])* $vis struct $name $($body)+); + }; + ($(#[$attr:meta])* $vis:vis struct $name:ident $($body:tt)+) => { + rpc_item!(Eq; $(#[$attr])* $vis struct $name $($body)+); + }; + (#[PartialEq] $(#[$attr:meta])* $vis:vis enum $name:ident $($body:tt)+) => { + rpc_item!(PartialEq; $(#[$attr])* $vis enum $name $($body)+); + }; + ($(#[$attr:meta])* $vis:vis enum $name:ident $($body:tt)+) => { + rpc_item!(Eq; $(#[$attr])* $vis enum $name $($body)+); + }; +} + +macro_rules! rpc_item { + (PartialEq; $(#[$attr:meta])* $vis:vis $kind:ident $name:ident $($body:tt)+) => { + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] + $(#[$attr])* + #[cfg_attr(feature = "frb", flutter_rust_bridge::frb(non_opaque))] + $vis $kind $name $($body)+ + + impl_codec!($name); + }; + (Eq; $(#[$attr:meta])* $vis:vis $kind:ident $name:ident $($body:tt)+) => { + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + $(#[$attr])* + #[cfg_attr(feature = "frb", flutter_rust_bridge::frb(non_opaque))] + $vis $kind $name $($body)+ + + impl_codec!($name); + }; +} diff --git a/ql-api/src/onboarding.rs b/ql-api/src/onboarding.rs new file mode 100644 index 00000000..24ce0263 --- /dev/null +++ b/ql-api/src/onboarding.rs @@ -0,0 +1,30 @@ +use ql_rpc::Notification; + +use crate::{route, Error}; + +rpc! { + pub enum OnboardingState { + SecurityChecked, + SecurityCheckFailed, + FirmwareUpdateScreen, + SecuringDevice, + DeviceSecured, + WalletCreationScreen, + CreatingWallet, + WalletCreated, + MagicBackupScreen, + CreatingMagicBackup, + MagicBackupCreated, + CreatingManualBackup, + CreatingKeycardBackup, + WritingDownSeedWords, + ConnectingWallet, + WalletConnected, + Completed, + } +} + +impl Notification for route::OnboardingStatus { + type Error = Error; + type Payload = OnboardingState; +} diff --git a/ql-api/src/scv.rs b/ql-api/src/scv.rs new file mode 100644 index 00000000..e4d60761 --- /dev/null +++ b/ql-api/src/scv.rs @@ -0,0 +1,44 @@ +use ql_rpc::Duplex; + +use crate::{route, Error}; + +rpc! { + pub struct ChallengeRequest { + pub data: Vec, + } +} + +rpc! { + pub enum ChallengeResponseResult { + Success { data: Vec }, + Error { error: String }, + } +} + +rpc! { + pub enum VerificationResult { + Success, + Error { error: String }, + Failure, + } +} + +rpc! { + pub enum SecurityCheckRemoteEvent { + ChallengeRequest(ChallengeRequest), + VerificationResult(VerificationResult), + } +} + +rpc! { + pub enum SecurityCheckPassportEvent { + Start, + ChallengeResponse(ChallengeResponseResult), + } +} + +impl Duplex for route::SecurityCheck { + type Error = Error; + type InitiatorEvent = SecurityCheckPassportEvent; + type ResponderEvent = SecurityCheckRemoteEvent; +} diff --git a/ql-api/src/status.rs b/ql-api/src/status.rs new file mode 100644 index 00000000..8a1ba269 --- /dev/null +++ b/ql-api/src/status.rs @@ -0,0 +1,52 @@ +use ql_rpc::{Notification, Request}; + +use crate::{route, Error}; + +rpc! { + pub enum PassportModel { + Gen1, + Gen2, + Prime, + } +} + +rpc! { + pub struct PassportFirmwareVersion(pub String); +} + +rpc! { + pub struct PassportSerial(pub String); +} + +rpc! { + pub enum PassportColor { + Light, + Dark, + } +} + +rpc! { + pub struct PassportDetailsRequest {} +} + +rpc! { + pub struct PassportDetailsResponse { + pub device_name: String, + pub model: PassportModel, + pub firmware_version: PassportFirmwareVersion, + pub serial: PassportSerial, + pub color: PassportColor, + pub onboarding_complete: bool, + } +} + +impl Request for route::PassportDetails { + type Error = Error; + type Request = PassportDetailsRequest; + type Response = PassportDetailsResponse; +} + +impl Notification for route::PassportDetailsUpdated { + type Error = Error; + type Payload = PassportDetailsResponse; +} diff --git a/ql-api/src/timezone.rs b/ql-api/src/timezone.rs new file mode 100644 index 00000000..4a44b924 --- /dev/null +++ b/ql-api/src/timezone.rs @@ -0,0 +1,20 @@ +use ql_rpc::Request; + +use crate::{route, Error}; + +rpc! { + pub struct TimezoneRequest {} +} + +rpc! { + pub struct TimezoneResponse { + pub offset_minutes: i32, + pub zone: String, + } +} + +impl Request for route::Timezone { + type Error = Error; + type Request = TimezoneRequest; + type Response = TimezoneResponse; +} From 649d8f915c1b291b566f51fa527e7e54cc89e85b Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Wed, 20 May 2026 09:56:20 -0400 Subject: [PATCH 04/13] ql-api: current time --- ql-api/src/lib.rs | 5 +++-- ql-api/src/{timezone.rs => time.rs} | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) rename ql-api/src/{timezone.rs => time.rs} (54%) diff --git a/ql-api/src/lib.rs b/ql-api/src/lib.rs index 2f694dd8..065c9f9f 100644 --- a/ql-api/src/lib.rs +++ b/ql-api/src/lib.rs @@ -15,7 +15,7 @@ mod fx; mod onboarding; mod scv; mod status; -mod timezone; +mod time; pub use app_store::*; pub use backup::*; @@ -26,7 +26,7 @@ pub use fx::*; pub use onboarding::*; pub use scv::*; pub use status::*; -pub use timezone::*; +pub use time::*; pub type Error = ciborium::de::Error; @@ -41,6 +41,7 @@ routes! { DeviceStatus = 300, EnvoyStatus = 301, Timezone = 303, + CurrentTime = 304, // status and presence PassportDetails = 402, diff --git a/ql-api/src/timezone.rs b/ql-api/src/time.rs similarity index 54% rename from ql-api/src/timezone.rs rename to ql-api/src/time.rs index 4a44b924..112bd550 100644 --- a/ql-api/src/timezone.rs +++ b/ql-api/src/time.rs @@ -18,3 +18,19 @@ impl Request for route::Timezone { type Request = TimezoneRequest; type Response = TimezoneResponse; } + +rpc! { + pub struct CurrentTimeRequest {} +} + +rpc! { + pub struct CurrentTimeResponse { + pub server_time_millis: u64, + } +} + +impl Request for route::CurrentTime { + type Error = Error; + type Request = CurrentTimeRequest; + type Response = CurrentTimeResponse; +} From 03a3391abaf4e17fddc6f218d1f776cb6946c253 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 29 Jun 2026 18:51:38 -0400 Subject: [PATCH 05/13] ql-api: separate service ids --- ql-api/src/bitcoin.rs | 5 ++ ql-api/src/lib.rs | 114 +++++++++++++++++++++++------------------- ql-api/src/macros.rs | 36 +++++++------ 3 files changed, 87 insertions(+), 68 deletions(-) diff --git a/ql-api/src/bitcoin.rs b/ql-api/src/bitcoin.rs index 9235247d..5443e9d8 100644 --- a/ql-api/src/bitcoin.rs +++ b/ql-api/src/bitcoin.rs @@ -55,6 +55,11 @@ impl Notification for route::AccountUpdate { type Payload = AccountUpdatePayload; } +impl Notification for route::PassportAccountUpdate { + type Error = Error; + type Payload = AccountUpdatePayload; +} + rpc! { pub struct ActiveSeedFingerprint { pub fingerprint: String, diff --git a/ql-api/src/lib.rs b/ql-api/src/lib.rs index 065c9f9f..d9225baa 100644 --- a/ql-api/src/lib.rs +++ b/ql-api/src/lib.rs @@ -30,58 +30,68 @@ pub use time::*; pub type Error = ciborium::de::Error; -pub const SERVICE_ID: ql_common::ServiceId = ql_common::ServiceId([0; 16]); +pub const FOUNDATION_APP: ql_common::ServiceId = ql_common::ServiceId([1; 16]); +pub const PASSPORT: ql_common::ServiceId = ql_common::ServiceId([2; 16]); routes! { - // app store - ListApps = 200, - AppDownload = 201, - - // status and presence - DeviceStatus = 300, - EnvoyStatus = 301, - Timezone = 303, - CurrentTime = 304, - - // status and presence - PassportDetails = 402, - PassportDetailsUpdated = 403, - - // security and onboarding - SecurityCheck = 500, - OnboardingStatus = 501, - - // firmware - FirmwareUpdateCheck = 600, - FirmwareDownload = 601, - FirmwareInstallStatus = 602, - - // market data - ExchangeRateUpdate = 700, - ExchangeRateSubscription = 701, - ExchangeRateHistory = 702, - PassportFiatPreference = 703, - PassportFiatPreferenceRequest = 704, - - // bitcoin and wallet - SignPsbt = 800, - BroadcastTransaction = 801, - AccountUpdate = 802, - PassportActiveSeedFingerprint = 803, - GetPassportActiveSeedFingerprint = 804, - - // backup - BackupShard = 900, - RestoreShard = 901, - EnvoyMagicBackupEnabled = 902, - PassportMagicBackupEnabled = 903, - PassportMagicBackupStatus = 904, - UploadMagicBackup = 905, - DownloadMagicBackup = 906, - RestoreMagicBackupComplete = 907, - - // debug - Echo = 1000, - BytesBenchmark = 1001, - DownloadBenchmark = 1002, + FOUNDATION_APP => FoundationAppRoute { + // app store + ListApps = 200, + AppDownload = 201, + + // time + Timezone = 303, + CurrentTime = 304, + + // status and presence + PassportDetailsUpdated = 403, + + // security and onboarding + SecurityCheck = 500, + OnboardingStatus = 501, + + // firmware + FirmwareUpdateCheck = 600, + FirmwareDownload = 601, + FirmwareInstallStatus = 602, + + // market data + ExchangeRateUpdate = 700, + ExchangeRateSubscription = 701, + ExchangeRateHistory = 702, + PassportFiatPreference = 703, + + // bitcoin and wallet + BroadcastTransaction = 801, + AccountUpdate = 802, + PassportActiveSeedFingerprint = 803, + + // backup + BackupShard = 900, + RestoreShard = 901, + EnvoyMagicBackupEnabled = 902, + PassportMagicBackupEnabled = 903, + PassportMagicBackupStatus = 904, + UploadMagicBackup = 905, + DownloadMagicBackup = 906, + RestoreMagicBackupComplete = 907, + + // debug + Echo = 1000, + BytesBenchmark = 1001, + DownloadBenchmark = 1002, + } + + PASSPORT => PassportRoute { + // status and presence + PassportDetails = 402, + + // market data + PassportFiatPreferenceRequest = 704, + + // bitcoin and wallet + SignPsbt = 800, + PassportAccountUpdate = 802, + GetPassportActiveSeedFingerprint = 804, + } } diff --git a/ql-api/src/macros.rs b/ql-api/src/macros.rs index 42d0a1f2..bc804f5c 100644 --- a/ql-api/src/macros.rs +++ b/ql-api/src/macros.rs @@ -17,28 +17,32 @@ macro_rules! impl_codec { } macro_rules! routes { - ($($route:ident = $id:literal,)*) => { - #[repr(u32)] - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum Route { - $($route = $id,)* - } + ($($service:ident => $route_enum:ident { $($route:ident = $id:literal,)* })*) => { + $( + #[repr(u32)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum $route_enum { + $($route = $id,)* + } - impl Route { - pub const fn id(self) -> ql_common::RouteId { - ql_common::RouteId::from_u32(self as u32) + impl $route_enum { + pub const fn id(self) -> ql_common::RouteId { + ql_common::RouteId::from_u32(self as u32) + } } - } + )* pub mod route { $( - #[derive(Debug)] - pub struct $route; + $( + #[derive(Debug)] + pub struct $route; - impl ql_rpc::Route for $route { - const SERVICE: ql_common::ServiceId = crate::SERVICE_ID; - const ROUTE: ql_common::RouteId = super::Route::$route.id(); - } + impl ql_rpc::Route for $route { + const SERVICE: ql_common::ServiceId = crate::$service; + const ROUTE: ql_common::RouteId = super::$route_enum::$route.id(); + } + )* )* } }; From ac04eece89676b3aadac80efa9d085cf71b05e6a Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 29 Jun 2026 18:55:47 -0400 Subject: [PATCH 06/13] ql-api: revamp --- Cargo.lock | 10 +++ Cargo.toml | 2 + ql-api/Cargo.toml | 1 + ql-api/src/app_store.rs | 61 ++++++++--------- ql-api/src/backup.rs | 137 ++++++++++++++++++++------------------- ql-api/src/benchmark.rs | 65 ------------------- ql-api/src/bitcoin.rs | 90 ++++++++++++++----------- ql-api/src/codec.rs | 66 ++++++++++++++++++- ql-api/src/debug.rs | 101 +++++++++++++++++++++++++++++ ql-api/src/firmware.rs | 110 ++++++++++++++++++------------- ql-api/src/fx.rs | 61 ++++++----------- ql-api/src/key.rs | 85 ++++++++++++++++++++++++ ql-api/src/lib.rs | 131 ++++++++++++------------------------- ql-api/src/macros.rs | 84 ++++++++++++++++++------ ql-api/src/onboarding.rs | 23 ++++--- ql-api/src/scv.rs | 22 +++++-- ql-api/src/settings.rs | 103 +++++++++++++++++++++++++++++ ql-api/src/status.rs | 52 --------------- ql-api/src/time.rs | 25 +++---- ql-keyos/Cargo.toml | 14 ++++ ql-keyos/src/lib.rs | 122 ++++++++++++++++++++++++++++++++++ 21 files changed, 887 insertions(+), 478 deletions(-) delete mode 100644 ql-api/src/benchmark.rs create mode 100644 ql-api/src/debug.rs create mode 100644 ql-api/src/key.rs create mode 100644 ql-api/src/settings.rs delete mode 100644 ql-api/src/status.rs create mode 100644 ql-keyos/Cargo.toml create mode 100644 ql-keyos/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 33639331..80af5db8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1373,6 +1373,7 @@ dependencies = [ "ciborium", "flutter_rust_bridge", "ql-common", + "ql-keyos", "ql-rpc", "serde", ] @@ -1404,6 +1405,15 @@ dependencies = [ "ql-wire", ] +[[package]] +name = "ql-keyos" +version = "0.1.0" +dependencies = [ + "bytes", + "ql-common", + "serde", +] + [[package]] name = "ql-rpc" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a61e9d98..ca1022e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "btp", "ql-codec", "ql-common", + "ql-keyos", "ql-api", "ql-fsm", "ql-rpc", @@ -28,5 +29,6 @@ btp = { path = "btp" } ql-codec = { path = "ql-codec" } ql-common = { path = "ql-common" } ql-fsm = { path = "ql-fsm" } +ql-keyos = { path = "ql-keyos" } ql-rpc = { path = "ql-rpc" } ql-wire = { path = "ql-wire" } diff --git a/ql-api/Cargo.toml b/ql-api/Cargo.toml index c81fbefb..3792167c 100644 --- a/ql-api/Cargo.toml +++ b/ql-api/Cargo.toml @@ -8,6 +8,7 @@ license = "Proprietary" [dependencies] ql-common = { workspace = true } +ql-keyos = { workspace = true, features = ["serde"] } ql-rpc = { workspace = true } bytes = { workspace = true } ciborium = "0.2.2" diff --git a/ql-api/src/app_store.rs b/ql-api/src/app_store.rs index 629b6a80..2463a3c0 100644 --- a/ql-api/src/app_store.rs +++ b/ql-api/src/app_store.rs @@ -1,72 +1,65 @@ use ql_rpc::{Download, Request}; -use crate::{route, Error}; +use crate::{Empty, Error}; + +// COMMON TYPES rpc! { #[derive(Copy)] pub struct AppVersion(pub u32, pub u32, pub u32); } -// -// LIST APPS -// - rpc! { - pub struct ListAppsRequest { - // + pub struct AppDetails { + pub name: String, + pub icon: String, // SVG? + pub developer_id: Vec, + pub app_id: Vec, } } -rpc! { - pub struct ListAppsResponse { - pub apps: Vec, +// APP ROUTES + +// SERVICE ROUTES + +service_routes! { + crate::service_id::APP_STORE => { + RequestListApps: Request = 1, + DownloadApp: Download = 2, } } rpc! { - pub struct AppDetails { - pub name: String, - pub icon: String, // SVG? - pub developer_id: Vec, - pub app_id: Vec, + pub struct ListAppsResponse { + pub apps: Vec, } } -impl Request for route::ListApps { +impl Request for RequestListApps { type Error = Error; - type Request = ListAppsRequest; + type Request = Empty; type Response = ListAppsResponse; } -// -// APP DOWNLOAD -// -// download a single tar file that contains: -// - app.elf -// - manifest.json - rpc! { - pub struct AppDownloadRequest { + pub struct DownloadAppParams { pub version: Option, } } rpc! { - pub struct AppDownloadHeader { + pub struct DownloadAppHeader { pub version: String, } } rpc! { - pub struct AppDownloadPartHeader {} + pub struct DownloadAppPartHeader {} } -impl Download for route::AppDownload { +impl Download for DownloadApp { type Error = Error; - - type Request = AppDownloadRequest; - - type ResponseHeader = AppDownloadHeader; - - type PartHeader = AppDownloadPartHeader; + type Request = DownloadAppParams; + type ResponseHeader = DownloadAppHeader; + type PartHeader = DownloadAppPartHeader; } diff --git a/ql-api/src/backup.rs b/ql-api/src/backup.rs index e2c32a46..f49a7996 100644 --- a/ql-api/src/backup.rs +++ b/ql-api/src/backup.rs @@ -1,6 +1,8 @@ -use ql_rpc::{Download, Notification, Request, Upload}; +use ql_rpc::{Download, Request, Subscription, Upload}; -use crate::{route, Error}; +use crate::{Empty, Error}; + +// COMMON TYPES rpc! { pub struct Shard(pub Vec); @@ -10,76 +12,95 @@ rpc! { pub struct SeedFingerprint(pub [u8; 32]); } -rpc! { - pub struct BackupShardRequest { - pub shard: Shard, +// APP ROUTES + +app_routes! { + crate::app_id::BACKUP => { + SubscribePassportMagicBackupEnabled: Subscription = 1, + SubscribeMagicBackupRestoreCompleted: Subscription = 2, } } rpc! { - pub enum BackupShardResponse { - Success, - Error { error: String }, + pub struct PassportMagicBackupEnabled { + pub enabled: bool, + pub seed_fingerprint: SeedFingerprint, } } -impl Request for route::BackupShard { +impl Subscription for SubscribePassportMagicBackupEnabled { type Error = Error; - type Request = BackupShardRequest; - type Response = BackupShardResponse; -} - -rpc! { - pub struct RestoreShardRequest { - pub seed_fingerprint: SeedFingerprint, - pub timestamp: Option, - } + type Request = Empty; + type Event = PassportMagicBackupEnabled; } rpc! { - pub enum RestoreShardResponse { - Success { shard: Shard }, + pub enum MagicBackupRestoreResult { + Success, Error { error: String }, - NotFound, } } -impl Request for route::RestoreShard { +impl Subscription for SubscribeMagicBackupRestoreCompleted { type Error = Error; - type Request = RestoreShardRequest; - type Response = RestoreShardResponse; + type Request = Empty; + type Event = MagicBackupRestoreResult; +} + +// SERVICE ROUTES + +service_routes! { + crate::service_id::BACKUP => { + RequestBackupShard: Request = 1, + RequestRestoreShard: Request = 2, + RequestPassportMagicBackupStatus: Request = 3, + UploadMagicBackup: Upload = 4, + DownloadMagicBackup: Download = 5, + } } rpc! { - pub struct EnvoyMagicBackupEnabledRequest {} + pub struct BackupShardParams { + pub shard: Shard, + } } rpc! { - pub struct EnvoyMagicBackupEnabledResponse { - pub enabled: bool, + pub enum BackupShardResponse { + Success, + Error { error: String }, } } -impl Request for route::EnvoyMagicBackupEnabled { +impl Request for RequestBackupShard { type Error = Error; - type Request = EnvoyMagicBackupEnabledRequest; - type Response = EnvoyMagicBackupEnabledResponse; + type Request = BackupShardParams; + type Response = BackupShardResponse; } rpc! { - pub struct PassportMagicBackupEnabledPayload { - pub enabled: bool, + pub struct RestoreShardParams { pub seed_fingerprint: SeedFingerprint, + pub timestamp: Option, + } +} + +rpc! { + pub enum RestoreShardResponse { + Success { shard: Shard }, + Error { error: String }, + NotFound, } } -impl Notification for route::PassportMagicBackupEnabled { +impl Request for RequestRestoreShard { type Error = Error; - type Payload = PassportMagicBackupEnabledPayload; + type Request = RestoreShardParams; + type Response = RestoreShardResponse; } rpc! { - pub struct PassportMagicBackupStatusRequest { + pub struct PassportMagicBackupStatusParams { pub seed_fingerprint: SeedFingerprint, pub timestamp: Option, } @@ -91,14 +112,14 @@ rpc! { } } -impl Request for route::PassportMagicBackupStatus { +impl Request for RequestPassportMagicBackupStatus { type Error = Error; - type Request = PassportMagicBackupStatusRequest; + type Request = PassportMagicBackupStatusParams; type Response = PassportMagicBackupStatusResponse; } rpc! { - pub struct UploadMagicBackupRequest { + pub struct UploadMagicBackupParams { pub seed_fingerprint: SeedFingerprint, pub total_size: Option, pub hash: [u8; 32], @@ -106,7 +127,7 @@ rpc! { } rpc! { - pub enum UploadMagicBackupResult { + pub enum UploadMagicBackupResponse { Success, Error { error: String }, } @@ -116,27 +137,19 @@ rpc! { pub struct UploadMagicBackupPartHeader {} } -impl Upload for route::UploadMagicBackup { +impl Upload for UploadMagicBackup { type Error = Error; - type Request = UploadMagicBackupRequest; + type Request = UploadMagicBackupParams; type PartHeader = UploadMagicBackupPartHeader; - type Response = UploadMagicBackupResult; + type Response = UploadMagicBackupResponse; } rpc! { - pub struct DownloadMagicBackupRequest { + pub struct DownloadMagicBackupParams { pub seed_fingerprint: SeedFingerprint, } } -rpc! { - pub enum DownloadMagicBackupHeader { - Found(BackupMetadata), - NotFound, - Error { error: String }, - } -} - rpc! { pub struct BackupMetadata { pub total_size: Option, @@ -144,24 +157,16 @@ rpc! { } rpc! { - pub struct DownloadMagicBackupPartHeader {} -} - -impl Download for route::DownloadMagicBackup { - type Error = Error; - type Request = DownloadMagicBackupRequest; - type ResponseHeader = DownloadMagicBackupHeader; - type PartHeader = DownloadMagicBackupPartHeader; -} - -rpc! { - pub enum RestoreMagicBackupResult { - Success, + pub enum DownloadMagicBackupHeader { + Found(BackupMetadata), + NotFound, Error { error: String }, } } -impl Notification for route::RestoreMagicBackupComplete { +impl Download for DownloadMagicBackup { type Error = Error; - type Payload = RestoreMagicBackupResult; + type Request = DownloadMagicBackupParams; + type ResponseHeader = DownloadMagicBackupHeader; + type PartHeader = Empty; } diff --git a/ql-api/src/benchmark.rs b/ql-api/src/benchmark.rs deleted file mode 100644 index 3409899c..00000000 --- a/ql-api/src/benchmark.rs +++ /dev/null @@ -1,65 +0,0 @@ -use ql_rpc::{request::Request, Download, Subscription}; - -use crate::{route, Error}; - -// Echo request-response benchmark -impl Request for route::Echo { - type Error = Error; - type Request = EchoRequest; - type Response = EchoResponse; -} - -rpc! { - pub struct EchoRequest { - pub message: String, - } -} - -rpc! { - pub struct EchoResponse { - pub message: String, - } -} - -// Byte-stream subscription benchmark -impl Subscription for route::BytesBenchmark { - type Error = Error; - type Event = BenchmarkEvent; - type Request = BenchmarkRequest; -} - -rpc! { - pub struct BenchmarkRequest { - pub length: u32, - } -} - -rpc! { - pub struct BenchmarkEvent { - pub bytes: Vec, - } -} - -// Raw-body download benchmark -impl Download for route::DownloadBenchmark { - type Error = Error; - type Request = DownloadBenchmarkRequest; - type ResponseHeader = DownloadBenchmarkHeader; - type PartHeader = DownloadBenchmarkPartHeader; -} - -rpc! { - pub struct DownloadBenchmarkRequest { - pub length: u64, - } -} - -rpc! { - pub struct DownloadBenchmarkHeader { - pub hash: Vec, - } -} - -rpc! { - pub struct DownloadBenchmarkPartHeader {} -} diff --git a/ql-api/src/bitcoin.rs b/ql-api/src/bitcoin.rs index 5443e9d8..8c2ab063 100644 --- a/ql-api/src/bitcoin.rs +++ b/ql-api/src/bitcoin.rs @@ -1,83 +1,97 @@ -use ql_rpc::{Notification, Request}; +use ql_rpc::{Request, Subscription}; -use crate::{route, Error}; +use crate::{Empty, Error}; -rpc! { - pub struct SignPsbtRequest { - pub account_id: String, - pub psbt: Vec, - } -} +// COMMON TYPES rpc! { - pub enum SignPsbtResponse { - Signed { psbt: Vec }, - Rejected, - Error { error: String }, + pub struct ActiveSeedFingerprint { + pub fingerprint: String, + pub has_passphrase: bool, } } -impl Request for route::SignPsbt { - type Error = Error; - type Request = SignPsbtRequest; - type Response = SignPsbtResponse; +// APP ROUTES + +app_routes! { + crate::app_id::BITCOIN => { + RequestSignPsbt: Request = 1, + SubscribeAccountUpdated: Subscription = 2, + RequestPassportActiveSeedFingerprint: Request = 3, + SubscribePassportActiveSeedFingerprint: Subscription = 4, + } } rpc! { - pub struct BroadcastTransactionRequest { + pub struct SignPsbtParams { pub account_id: String, pub psbt: Vec, } } rpc! { - pub enum BroadcastTransactionResponse { - Broadcast { txid: String }, + pub enum SignPsbtResponse { + Signed { psbt: Vec }, + Rejected, Error { error: String }, } } -impl Request for route::BroadcastTransaction { +impl Request for RequestSignPsbt { type Error = Error; - type Request = BroadcastTransactionRequest; - type Response = BroadcastTransactionResponse; + type Request = SignPsbtParams; + type Response = SignPsbtResponse; } rpc! { - pub struct AccountUpdatePayload { + pub struct AccountUpdatedEvent { pub account_id: String, pub update: Vec, } } -impl Notification for route::AccountUpdate { +impl Subscription for SubscribeAccountUpdated { type Error = Error; - type Payload = AccountUpdatePayload; + type Request = Empty; + type Event = AccountUpdatedEvent; } -impl Notification for route::PassportAccountUpdate { +impl Subscription for SubscribePassportActiveSeedFingerprint { type Error = Error; - type Payload = AccountUpdatePayload; + type Request = Empty; + type Event = ActiveSeedFingerprint; } -rpc! { - pub struct ActiveSeedFingerprint { - pub fingerprint: String, - pub has_passphrase: bool, +impl Request for RequestPassportActiveSeedFingerprint { + type Error = Error; + type Request = Empty; + type Response = ActiveSeedFingerprint; +} + +// SERVICE ROUTES + +service_routes! { + crate::service_id::BITCOIN => { + RequestBroadcastTransaction: Request = 1, } } -impl Notification for route::PassportActiveSeedFingerprint { - type Error = Error; - type Payload = ActiveSeedFingerprint; +rpc! { + pub struct BroadcastTransactionParams { + pub account_id: String, + pub psbt: Vec, + } } rpc! { - pub struct PassportActiveSeedFingerprintRequest {} + pub enum BroadcastTransactionResponse { + Broadcast { txid: String }, + Error { error: String }, + } } -impl Request for route::GetPassportActiveSeedFingerprint { +impl Request for RequestBroadcastTransaction { type Error = Error; - type Request = PassportActiveSeedFingerprintRequest; - type Response = ActiveSeedFingerprint; + type Request = BroadcastTransactionParams; + type Response = BroadcastTransactionResponse; } diff --git a/ql-api/src/codec.rs b/ql-api/src/codec.rs index eda25685..a90ce607 100644 --- a/ql-api/src/codec.rs +++ b/ql-api/src/codec.rs @@ -1,10 +1,27 @@ use std::io; use bytes::{Buf, BufMut}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::Error; +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "frb", flutter_rust_bridge::frb(non_opaque))] +pub struct Empty {} + +impl ql_rpc::RpcCodec for Empty { + type Error = Error; + + fn encode_value(&self, out: &mut B) { + encode_cbor(&self, out); + } + + fn decode_value(bytes: &mut B) -> Result { + decode_cbor::(bytes)?; + Ok(Self {}) + } +} + pub fn encode_cbor(value: &T, out: &mut B) where T: Serialize, @@ -54,3 +71,50 @@ impl io::Read for BufReader<'_, B> { Ok(len) } } + +#[cfg(test)] +mod tests { + use bytes::BytesMut; + + use super::*; + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct OldHeader {} + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct NewHeader { + field: Option, + } + + #[test] + fn cbor_empty_struct_can_evolve_with_optional_fields() { + let mut old_bytes = BytesMut::new(); + encode_cbor(&OldHeader {}, &mut old_bytes); + + let decoded_new: NewHeader = decode_cbor(&mut old_bytes.freeze()).unwrap(); + assert_eq!(decoded_new, NewHeader { field: None }); + + let mut new_bytes = BytesMut::new(); + encode_cbor(&NewHeader { field: Some(7) }, &mut new_bytes); + + let decoded_old: OldHeader = decode_cbor(&mut new_bytes.freeze()).unwrap(); + assert_eq!(decoded_old, OldHeader {}); + } + + #[test] + fn empty_codec_can_evolve_into_cbor_fields() { + let mut empty_bytes = BytesMut::new(); + ql_rpc::RpcCodec::encode_value(&Empty {}, &mut empty_bytes); + assert!(!empty_bytes.is_empty()); + + let decoded_new: NewHeader = decode_cbor(&mut empty_bytes.freeze()).unwrap(); + assert_eq!(decoded_new, NewHeader { field: None }); + + let mut new_bytes = BytesMut::new(); + encode_cbor(&NewHeader { field: Some(7) }, &mut new_bytes); + + let decoded_empty = + ::decode_value(&mut new_bytes.freeze()).unwrap(); + assert_eq!(decoded_empty, Empty {}); + } +} diff --git a/ql-api/src/debug.rs b/ql-api/src/debug.rs new file mode 100644 index 00000000..93e35eb9 --- /dev/null +++ b/ql-api/src/debug.rs @@ -0,0 +1,101 @@ +use ql_rpc::{request::Request, Download, Subscription}; + +use crate::Error; + +// APP ROUTES + +app_routes! { + crate::app_id::DEBUG => { + RequestPassportEcho: Request = 1, + SubscribePassportBytesBenchmark: Subscription = 2, + DownloadPassportBenchmark: Download = 3, + } +} + +rpc! { + pub struct EchoParams { + pub message: String, + } +} + +rpc! { + pub struct EchoResponse { + pub message: String, + } +} + +rpc! { + pub struct BytesBenchmarkParams { + pub length: u32, + } +} + +rpc! { + pub struct BytesBenchmarkEvent { + pub bytes: Vec, + } +} + +rpc! { + pub struct DownloadBenchmarkParams { + pub length: u64, + } +} + +rpc! { + pub struct DownloadBenchmarkHeader { + pub hash: Vec, + } +} + +rpc! { + pub struct DownloadBenchmarkPartHeader {} +} + +impl Request for RequestPassportEcho { + type Error = Error; + type Request = EchoParams; + type Response = EchoResponse; +} + +impl Subscription for SubscribePassportBytesBenchmark { + type Error = Error; + type Event = BytesBenchmarkEvent; + type Request = BytesBenchmarkParams; +} + +impl Download for DownloadPassportBenchmark { + type Error = Error; + type Request = DownloadBenchmarkParams; + type ResponseHeader = DownloadBenchmarkHeader; + type PartHeader = DownloadBenchmarkPartHeader; +} + +// SERVICE ROUTES + +service_routes! { + crate::service_id::DEBUG => { + RequestEcho: Request = 1, + SubscribeBytesBenchmark: Subscription = 2, + DownloadBenchmark: Download = 3, + } +} + +impl Request for RequestEcho { + type Error = Error; + type Request = EchoParams; + type Response = EchoResponse; +} + +impl Subscription for SubscribeBytesBenchmark { + type Error = Error; + type Event = BytesBenchmarkEvent; + type Request = BytesBenchmarkParams; +} + +impl Download for DownloadBenchmark { + type Error = Error; + type Request = DownloadBenchmarkParams; + type ResponseHeader = DownloadBenchmarkHeader; + type PartHeader = DownloadBenchmarkPartHeader; +} diff --git a/ql-api/src/firmware.rs b/ql-api/src/firmware.rs index bf0b49e0..c884d8f0 100644 --- a/ql-api/src/firmware.rs +++ b/ql-api/src/firmware.rs @@ -1,87 +1,107 @@ -use ql_rpc::{Download, Notification, Request}; +use ql_rpc::{Download, Request, Subscription}; -use crate::{route, Error}; +use crate::{Empty, Error}; + +// COMMON TYPES rpc! { - pub struct FirmwareUpdateCheckRequest { - pub current_version: String, + pub struct FirmwareUpdateAvailable { + pub version: String, + pub changelog: String, + pub timestamp: u32, + pub total_size: u32, + pub patch_count: u8, + } +} + +// APP ROUTES + +app_routes! { + crate::app_id::UPDATE => { + SubscribeFirmwareInstallStatus: Subscription = 1, } } rpc! { - pub enum FirmwareUpdateCheckResponse { - Available(FirmwareUpdateAvailable), - NotAvailable, + pub enum InstallErrorStage { + Download, + Verify, + Install, } } rpc! { - pub struct FirmwareUpdateAvailable { - pub version: String, - pub changelog: String, - pub timestamp: u32, - pub total_size: u32, - pub patch_count: u8, + pub enum FirmwareInstallEvent { + Installing, + Rebooting, + Success { + installed_version: String, + }, + Error { + error: String, + stage: InstallErrorStage, + }, } } -impl Request for route::FirmwareUpdateCheck { +impl Subscription for SubscribeFirmwareInstallStatus { type Error = Error; - type Request = FirmwareUpdateCheckRequest; - type Response = FirmwareUpdateCheckResponse; + type Request = Empty; + type Event = FirmwareInstallEvent; +} + +// SERVICE ROUTES + +service_routes! { + crate::service_id::FIRMWARE => { + RequestCheckFirmwareUpdate: Request = 1, + DownloadFirmware: Download = 2, + } } rpc! { - pub struct FirmwareDownloadRequest { + pub struct FirmwareUpdateCheckParams { pub current_version: String, } } rpc! { - pub enum FirmwareDownloadHeader { + pub enum FirmwareUpdateCheckResponse { Available(FirmwareUpdateAvailable), NotAvailable, - Error { error: String }, } } -rpc! { - pub struct FirmwareDownloadPartHeader { - pub patch_name: String, - pub size_bytes: u64, - } +impl Request for RequestCheckFirmwareUpdate { + type Error = Error; + type Request = FirmwareUpdateCheckParams; + type Response = FirmwareUpdateCheckResponse; } -impl Download for route::FirmwareDownload { - type Error = Error; - type Request = FirmwareDownloadRequest; - type ResponseHeader = FirmwareDownloadHeader; - type PartHeader = FirmwareDownloadPartHeader; +rpc! { + pub struct DownloadFirmwareParams { + pub current_version: String, + } } rpc! { - pub enum FirmwareInstallEvent { - Installing, - Rebooting, - Success { - installed_version: String, - }, - Error { - error: String, - stage: InstallErrorStage, - }, + pub enum DownloadFirmwareHeader { + Available(FirmwareUpdateAvailable), + NotAvailable, + Error { error: String }, } } rpc! { - pub enum InstallErrorStage { - Download, - Verify, - Install, + pub struct DownloadFirmwarePartHeader { + pub patch_name: String, + pub size_bytes: u64, } } -impl Notification for route::FirmwareInstallStatus { +impl Download for DownloadFirmware { type Error = Error; - type Payload = FirmwareInstallEvent; + type Request = DownloadFirmwareParams; + type ResponseHeader = DownloadFirmwareHeader; + type PartHeader = DownloadFirmwarePartHeader; } diff --git a/ql-api/src/fx.rs b/ql-api/src/fx.rs index 260cf3d4..aa561fdc 100644 --- a/ql-api/src/fx.rs +++ b/ql-api/src/fx.rs @@ -1,6 +1,8 @@ -use ql_rpc::{Notification, Request, Subscription}; +use ql_rpc::{Request, Subscription}; -use crate::{route, Error}; +use crate::Error; + +// COMMON TYPES rpc! { #[PartialEq] @@ -19,66 +21,45 @@ rpc! { } } -rpc! { - pub struct ExchangeRateHistoryRequest { - pub currency_code: String, - } -} +// APP ROUTES -rpc! { - #[PartialEq] - pub struct ExchangeRateHistory { - pub history: Vec, - pub currency_code: String, - } -} +// SERVICE ROUTES -impl Notification for route::ExchangeRateUpdate { - type Error = Error; - type Payload = ExchangeRate; +service_routes! { + crate::service_id::FX => { + SubscribeExchangeRate: Subscription = 1, + RequestExchangeRateHistory: Request = 2, + } } rpc! { - pub struct ExchangeRateSubscriptionRequest { + pub struct ExchangeRateParams { pub currency_code: String, } } -impl Subscription for route::ExchangeRateSubscription { +impl Subscription for SubscribeExchangeRate { type Error = Error; - type Request = ExchangeRateSubscriptionRequest; + type Request = ExchangeRateParams; type Event = ExchangeRate; } -impl Request for route::ExchangeRateHistory { - type Error = Error; - type Request = ExchangeRateHistoryRequest; - type Response = ExchangeRateHistory; -} - rpc! { - pub struct PassportFiatPreferencePayload { + pub struct ExchangeRateHistoryParams { pub currency_code: String, } } -impl Notification for route::PassportFiatPreference { - type Error = Error; - type Payload = PassportFiatPreferencePayload; -} - rpc! { - pub struct PassportFiatPreferenceRequest {} -} - -rpc! { - pub struct PassportFiatPreferenceResponse { + #[PartialEq] + pub struct ExchangeRateHistory { + pub history: Vec, pub currency_code: String, } } -impl Request for route::PassportFiatPreferenceRequest { +impl Request for RequestExchangeRateHistory { type Error = Error; - type Request = PassportFiatPreferenceRequest; - type Response = PassportFiatPreferenceResponse; + type Request = ExchangeRateHistoryParams; + type Response = ExchangeRateHistory; } diff --git a/ql-api/src/key.rs b/ql-api/src/key.rs new file mode 100644 index 00000000..2646dceb --- /dev/null +++ b/ql-api/src/key.rs @@ -0,0 +1,85 @@ +use ql_keyos::{AppId, RouteId, ServiceId}; + +pub mod app_id { + use ql_keyos::AppId; + + pub const SYSTEM: AppId = AppId::from_hex("0x6f732f716c0000000000000000000000"); + pub const ONBOARDING: AppId = AppId::from_hex("0xdac5321775d449c11bc9c90f38067f8f"); + pub const BACKUP: AppId = AppId::from_hex("0xe19f16fcd9610bca7d026b4673f1cb06"); + pub const UPDATE: AppId = AppId::from_hex("0x6F732F75706461746500000000000000"); + pub const BITCOIN: AppId = AppId::from_hex("0x426974636f696e2057616c6c65740000"); + pub const DEBUG: AppId = AppId::from_hex("0x8ec0b4ea99704f9f973f5d7b7a3294b2"); +} + +pub mod service_id { + use ql_keyos::ServiceId; + + pub const APP_STORE: ServiceId = ServiceId::from_u32(1); + pub const BACKUP: ServiceId = ServiceId::from_u32(2); + pub const BITCOIN: ServiceId = ServiceId::from_u32(3); + pub const DEBUG: ServiceId = ServiceId::from_u32(4); + pub const FIRMWARE: ServiceId = ServiceId::from_u32(5); + pub const FX: ServiceId = ServiceId::from_u32(6); + pub const SCV: ServiceId = ServiceId::from_u32(7); + pub const TIME: ServiceId = ServiceId::from_u32(8); +} + +/// Route key for a remote peer rpc +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ServiceRouteKey { + pub service_id: ServiceId, + pub route_id: RouteId, +} + +impl ql_rpc::RpcRouteKey for ServiceRouteKey { + fn encoded_len(&self) -> usize { + self.service_id.0.size() + self.route_id.0.size() + } + + fn encode(&self, out: &mut W) { + self.service_id.0.write_bytes(|bytes| out.put_slice(bytes)); + self.route_id.0.write_bytes(|bytes| out.put_slice(bytes)); + } + + fn decode(bytes: &[u8]) -> Option { + let (service_id, bytes) = ql_common::VarInt::decode_bytes(bytes)?; + let (route_id, rest) = ql_common::VarInt::decode_bytes(bytes)?; + rest.is_empty().then_some(Self { + service_id: ServiceId(service_id), + route_id: RouteId(route_id), + }) + } +} + +impl ql_keyos::ServiceTargetKey for ServiceRouteKey { + fn service_id(&self) -> ServiceId { + self.service_id + } +} + +/// Route key for an keyos handled rpc +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AppRouteKey { + pub app_id: AppId, + pub route_id: RouteId, +} + +impl ql_rpc::RpcRouteKey for AppRouteKey { + fn encoded_len(&self) -> usize { + AppId::SIZE + self.route_id.0.size() + } + + fn encode(&self, out: &mut W) { + out.put_slice(&self.app_id.0); + self.route_id.0.write_bytes(|bytes| out.put_slice(bytes)); + } + + fn decode(bytes: &[u8]) -> Option { + let app_id = AppId(bytes.get(..AppId::SIZE)?.try_into().ok()?); + let (route_id, rest) = ql_common::VarInt::decode_bytes(&bytes[AppId::SIZE..])?; + rest.is_empty().then_some(Self { + app_id, + route_id: RouteId(route_id), + }) + } +} diff --git a/ql-api/src/lib.rs b/ql-api/src/lib.rs index d9225baa..839676d9 100644 --- a/ql-api/src/lib.rs +++ b/ql-api/src/lib.rs @@ -1,97 +1,46 @@ -// 1. list catalog of apps (request/response) -// 2. get app by id -// 3. download version? - #[macro_use] mod macros; -mod app_store; -mod backup; -mod benchmark; -mod bitcoin; -mod codec; -mod firmware; -mod fx; -mod onboarding; -mod scv; -mod status; -mod time; - -pub use app_store::*; -pub use backup::*; -pub use benchmark::*; -pub use bitcoin::*; -pub use firmware::*; -pub use fx::*; -pub use onboarding::*; -pub use scv::*; -pub use status::*; -pub use time::*; +pub mod app_store; +pub mod backup; +pub mod bitcoin; +pub mod codec; +pub mod debug; +pub mod firmware; +pub mod fx; +pub mod key; +pub mod onboarding; +pub mod scv; +pub mod settings; +pub mod time; + +pub use self::{ + app_store::*, backup::*, bitcoin::*, codec::Empty, debug::*, firmware::*, fx::*, key::*, + onboarding::*, scv::*, settings::*, time::*, +}; pub type Error = ciborium::de::Error; -pub const FOUNDATION_APP: ql_common::ServiceId = ql_common::ServiceId([1; 16]); -pub const PASSPORT: ql_common::ServiceId = ql_common::ServiceId([2; 16]); - -routes! { - FOUNDATION_APP => FoundationAppRoute { - // app store - ListApps = 200, - AppDownload = 201, - - // time - Timezone = 303, - CurrentTime = 304, - - // status and presence - PassportDetailsUpdated = 403, - - // security and onboarding - SecurityCheck = 500, - OnboardingStatus = 501, - - // firmware - FirmwareUpdateCheck = 600, - FirmwareDownload = 601, - FirmwareInstallStatus = 602, - - // market data - ExchangeRateUpdate = 700, - ExchangeRateSubscription = 701, - ExchangeRateHistory = 702, - PassportFiatPreference = 703, - - // bitcoin and wallet - BroadcastTransaction = 801, - AccountUpdate = 802, - PassportActiveSeedFingerprint = 803, - - // backup - BackupShard = 900, - RestoreShard = 901, - EnvoyMagicBackupEnabled = 902, - PassportMagicBackupEnabled = 903, - PassportMagicBackupStatus = 904, - UploadMagicBackup = 905, - DownloadMagicBackup = 906, - RestoreMagicBackupComplete = 907, - - // debug - Echo = 1000, - BytesBenchmark = 1001, - DownloadBenchmark = 1002, - } - - PASSPORT => PassportRoute { - // status and presence - PassportDetails = 402, - - // market data - PassportFiatPreferenceRequest = 704, - - // bitcoin and wallet - SignPsbt = 800, - PassportAccountUpdate = 802, - GetPassportActiveSeedFingerprint = 804, - } -} +// naming scheme +// Request: +// - Request{Thing} +// - {Thing}Params +// - {Thing}Response +// Subscription: +// - Subscribe{Thing} +// - {Thing}Params +// - {Thing}Event +// Download: +// - Download{Thing} +// - Download{Thing}Params +// - Download{Thing}Header +// - Download{Thing}PartHeader +// Upload: +// - Upload{Thing} +// - Upload{Thing}Params +// - Upload{Thing}PartHeader +// - Upload{Thing}Response +// Duplex: +// - {Action}{Thing} +// - {Action}{Thing}InitiatorEvent +// - {Action}{Thing}ResponderEvent diff --git a/ql-api/src/macros.rs b/ql-api/src/macros.rs index bc804f5c..e700964f 100644 --- a/ql-api/src/macros.rs +++ b/ql-api/src/macros.rs @@ -16,35 +16,77 @@ macro_rules! impl_codec { }; } -macro_rules! routes { - ($($service:ident => $route_enum:ident { $($route:ident = $id:literal,)* })*) => { - $( - #[repr(u32)] - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum $route_enum { - $($route = $id,)* +macro_rules! duplicate_route_check { + ($($id:literal,)*) => { + const _: () = { + const IDS: &[u32] = &[$($id,)*]; + let mut i = 0; + while i < IDS.len() { + let mut j = i + 1; + while j < IDS.len() { + assert!(IDS[i] != IDS[j], "duplicate route id"); + j += 1; + } + i += 1; } + }; + }; +} + +macro_rules! assert_route_impl { + ($route:ident: $kind:ident) => { + const _: () = { + struct Assert(core::marker::PhantomData); + let _ = Assert::<$route>(core::marker::PhantomData); + }; + }; +} + +macro_rules! service_routes { + ($service_id:expr => { $($route:ident: $kind:ident = $id:literal,)* }) => { + duplicate_route_check!($($id,)*); + + $( + #[derive(Debug)] + pub struct $route; + + impl ql_rpc::Route for $route { + type Key = crate::ServiceRouteKey; - impl $route_enum { - pub const fn id(self) -> ql_common::RouteId { - ql_common::RouteId::from_u32(self as u32) + fn key() -> Self::Key { + crate::ServiceRouteKey { + service_id: $service_id, + route_id: ql_keyos::RouteId::from_u32($id), + } } } + + assert_route_impl!($route: $kind); )* + }; +} + +macro_rules! app_routes { + ($app_id:expr => { $($route:ident: $kind:ident = $id:literal,)* }) => { + duplicate_route_check!($($id,)*); + + $( + #[derive(Debug)] + pub struct $route; - pub mod route { - $( - $( - #[derive(Debug)] - pub struct $route; + impl ql_rpc::Route for $route { + type Key = crate::AppRouteKey; - impl ql_rpc::Route for $route { - const SERVICE: ql_common::ServiceId = crate::$service; - const ROUTE: ql_common::RouteId = super::$route_enum::$route.id(); + fn key() -> Self::Key { + crate::AppRouteKey { + app_id: $app_id, + route_id: ql_keyos::RouteId::from_u32($id), } - )* - )* - } + } + } + + assert_route_impl!($route: $kind); + )* }; } diff --git a/ql-api/src/onboarding.rs b/ql-api/src/onboarding.rs index 24ce0263..1a5ac9e6 100644 --- a/ql-api/src/onboarding.rs +++ b/ql-api/src/onboarding.rs @@ -1,11 +1,17 @@ -use ql_rpc::Notification; +use ql_rpc::Subscription; -use crate::{route, Error}; +use crate::{Empty, Error}; + +// APP ROUTES + +app_routes! { + crate::app_id::ONBOARDING => { + SubscribeOnboardingStatus: Subscription = 1, + } +} rpc! { pub enum OnboardingState { - SecurityChecked, - SecurityCheckFailed, FirmwareUpdateScreen, SecuringDevice, DeviceSecured, @@ -18,13 +24,14 @@ rpc! { CreatingManualBackup, CreatingKeycardBackup, WritingDownSeedWords, - ConnectingWallet, - WalletConnected, Completed, } } -impl Notification for route::OnboardingStatus { +impl Subscription for SubscribeOnboardingStatus { type Error = Error; - type Payload = OnboardingState; + type Request = Empty; + type Event = OnboardingState; } + +// SERVICE ROUTES diff --git a/ql-api/src/scv.rs b/ql-api/src/scv.rs index e4d60761..4c7e2a8d 100644 --- a/ql-api/src/scv.rs +++ b/ql-api/src/scv.rs @@ -1,6 +1,16 @@ use ql_rpc::Duplex; -use crate::{route, Error}; +use crate::Error; + +// APP ROUTES + +// SERVICE ROUTES + +service_routes! { + crate::service_id::SCV => { + RunSecurityCheck: Duplex = 1, + } +} rpc! { pub struct ChallengeRequest { @@ -24,21 +34,21 @@ rpc! { } rpc! { - pub enum SecurityCheckRemoteEvent { + pub enum RunSecurityCheckResponderEvent { ChallengeRequest(ChallengeRequest), VerificationResult(VerificationResult), } } rpc! { - pub enum SecurityCheckPassportEvent { + pub enum RunSecurityCheckInitiatorEvent { Start, ChallengeResponse(ChallengeResponseResult), } } -impl Duplex for route::SecurityCheck { +impl Duplex for RunSecurityCheck { type Error = Error; - type InitiatorEvent = SecurityCheckPassportEvent; - type ResponderEvent = SecurityCheckRemoteEvent; + type InitiatorEvent = RunSecurityCheckInitiatorEvent; + type ResponderEvent = RunSecurityCheckResponderEvent; } diff --git a/ql-api/src/settings.rs b/ql-api/src/settings.rs new file mode 100644 index 00000000..c09e452d --- /dev/null +++ b/ql-api/src/settings.rs @@ -0,0 +1,103 @@ +use ql_keyos::AppId; +use ql_rpc::{Request, Subscription}; + +use crate::{Empty, Error}; + +// COMMON TYPES + +rpc! { + pub enum PassportModel { + Gen1, + Gen2, + Prime, + } +} + +rpc! { + pub struct PassportFirmwareVersion(pub String); +} + +rpc! { + pub struct PassportSerial(pub String); +} + +rpc! { + pub enum PassportColor { + Light, + Dark, + } +} + +// APP ROUTES + +app_routes! { + crate::app_id::SYSTEM => { + RequestPassportDetails: Request = 1, + SubscribePassportDetails: Subscription = 2, + RequestPassportFiatPreference: Request = 3, + SubscribePassportFiatPreference: Subscription = 4, + SubscribeAppActivity: Subscription = 5, + } +} + +rpc! { + pub struct PassportDetailsResponse { + pub device_name: String, + pub model: PassportModel, + pub firmware_version: PassportFirmwareVersion, + pub serial: PassportSerial, + pub color: PassportColor, + pub onboarding_complete: bool, + } +} + +impl Request for RequestPassportDetails { + type Error = Error; + type Request = Empty; + type Response = PassportDetailsResponse; +} + +impl Subscription for SubscribePassportDetails { + type Error = Error; + type Request = Empty; + type Event = PassportDetailsResponse; +} + +rpc! { + pub struct PassportFiatPreferenceResponse { + pub currency_code: String, + } +} + +impl Request for RequestPassportFiatPreference { + type Error = Error; + type Request = Empty; + type Response = PassportFiatPreferenceResponse; +} + +rpc! { + pub struct PassportFiatPreferenceEvent { + pub currency_code: String, + } +} + +impl Subscription for SubscribePassportFiatPreference { + type Error = Error; + type Request = Empty; + type Event = PassportFiatPreferenceEvent; +} + +rpc! { + pub enum AppActivityEvent { + Opened(AppId), + Closed(AppId), + } +} + +impl Subscription for SubscribeAppActivity { + type Error = Error; + type Request = Empty; + type Event = AppActivityEvent; +} + +// SERVICE ROUTES diff --git a/ql-api/src/status.rs b/ql-api/src/status.rs deleted file mode 100644 index 8a1ba269..00000000 --- a/ql-api/src/status.rs +++ /dev/null @@ -1,52 +0,0 @@ -use ql_rpc::{Notification, Request}; - -use crate::{route, Error}; - -rpc! { - pub enum PassportModel { - Gen1, - Gen2, - Prime, - } -} - -rpc! { - pub struct PassportFirmwareVersion(pub String); -} - -rpc! { - pub struct PassportSerial(pub String); -} - -rpc! { - pub enum PassportColor { - Light, - Dark, - } -} - -rpc! { - pub struct PassportDetailsRequest {} -} - -rpc! { - pub struct PassportDetailsResponse { - pub device_name: String, - pub model: PassportModel, - pub firmware_version: PassportFirmwareVersion, - pub serial: PassportSerial, - pub color: PassportColor, - pub onboarding_complete: bool, - } -} - -impl Request for route::PassportDetails { - type Error = Error; - type Request = PassportDetailsRequest; - type Response = PassportDetailsResponse; -} - -impl Notification for route::PassportDetailsUpdated { - type Error = Error; - type Payload = PassportDetailsResponse; -} diff --git a/ql-api/src/time.rs b/ql-api/src/time.rs index 112bd550..3033cf67 100644 --- a/ql-api/src/time.rs +++ b/ql-api/src/time.rs @@ -1,9 +1,16 @@ use ql_rpc::Request; -use crate::{route, Error}; +use crate::{Empty, Error}; -rpc! { - pub struct TimezoneRequest {} +// APP ROUTES + +// SERVICE ROUTES + +service_routes! { + crate::service_id::TIME => { + RequestTimezone: Request = 1, + RequestCurrentTime: Request = 2, + } } rpc! { @@ -13,24 +20,20 @@ rpc! { } } -impl Request for route::Timezone { +impl Request for RequestTimezone { type Error = Error; - type Request = TimezoneRequest; + type Request = Empty; type Response = TimezoneResponse; } -rpc! { - pub struct CurrentTimeRequest {} -} - rpc! { pub struct CurrentTimeResponse { pub server_time_millis: u64, } } -impl Request for route::CurrentTime { +impl Request for RequestCurrentTime { type Error = Error; - type Request = CurrentTimeRequest; + type Request = Empty; type Response = CurrentTimeResponse; } diff --git a/ql-keyos/Cargo.toml b/ql-keyos/Cargo.toml new file mode 100644 index 00000000..aa193c89 --- /dev/null +++ b/ql-keyos/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ql-keyos" +version = "0.1.0" +edition = "2021" +description = "KeyOS QuantumLink addressing primitives" +license = "Proprietary" + +[dependencies] +bytes = { workspace = true } +ql-common = { workspace = true } +serde = { version = "1.0", features = ["derive"], optional = true } + +[features] +serde = ["dep:serde"] diff --git a/ql-keyos/src/lib.rs b/ql-keyos/src/lib.rs new file mode 100644 index 00000000..b1d8bb4c --- /dev/null +++ b/ql-keyos/src/lib.rs @@ -0,0 +1,122 @@ +//! KeyOS addressing primitives for QuantumLink + +use bytes::BufMut; +pub use ql_common::VarInt; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[repr(transparent)] +pub struct AppId(pub [u8; Self::SIZE]); + +impl AppId { + pub const SIZE: usize = 16; + + pub const fn from_hex(hex: &str) -> Self { + const fn hex_nibble(byte: u8) -> u8 { + match byte { + b'0'..=b'9' => byte - b'0', + b'a'..=b'f' => byte - b'a' + 10, + b'A'..=b'F' => byte - b'A' + 10, + _ => panic!("invalid hex digit"), + } + } + let bytes = hex.as_bytes(); + if bytes.len() != Self::SIZE * 2 + 2 || bytes[0] != b'0' || bytes[1] != b'x' { + panic!("app id must be 0x followed by 32 hex digits") + } + + let mut app_id = [0; Self::SIZE]; + let mut i = 0; + while i < Self::SIZE { + app_id[i] = (hex_nibble(bytes[i * 2 + 2]) << 4) | hex_nibble(bytes[i * 2 + 3]); + i += 1; + } + + Self(app_id) + } +} + +ql_common::varint_wrapper!( + /// Identifier for a route within a KeyOS app or service namespace + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "serde", serde(into = "u64", try_from = "u64"))] + RouteId +); + +ql_common::varint_wrapper!( + /// Identifier for a KeyOS service advertised by a peer + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "serde", serde(into = "u64", try_from = "u64"))] + ServiceId +); + +pub trait ServiceTargetKey { + fn service_id(&self) -> ServiceId; +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PeerMetadata { + /// KeyOS app ids this peer is allowed to open streams toward + pub app_ids: Vec, + /// Service ids this peer advertises for KeyOS-opened streams + pub service_ids: Vec, +} + +impl PeerMetadata { + pub fn write_bytes(&self, out: &mut B) { + write_varint(len_varint(self.app_ids.len()), out); + for app_id in &self.app_ids { + out.put_slice(&app_id.0); + } + + write_varint(len_varint(self.service_ids.len()), out); + for service_id in &self.service_ids { + write_varint(service_id.0, out); + } + } + + pub fn from_bytes(mut bytes: &[u8]) -> Option { + let app_count = read_len(&mut bytes)?; + let app_bytes_len = app_count.checked_mul(AppId::SIZE)?; + if app_bytes_len > bytes.len() { + return None; + } + let mut app_ids = Vec::with_capacity(app_count); + for _ in 0..app_count { + let app_id = AppId(bytes.get(..AppId::SIZE)?.try_into().ok()?); + bytes = &bytes[AppId::SIZE..]; + app_ids.push(app_id); + } + + let service_count = read_len(&mut bytes)?; + if service_count > bytes.len() { + return None; + } + let mut service_ids = Vec::with_capacity(service_count); + for _ in 0..service_count { + let (service_id, rest) = VarInt::decode_bytes(bytes)?; + bytes = rest; + service_ids.push(ServiceId(service_id)); + } + + bytes.is_empty().then_some(Self { + app_ids, + service_ids, + }) + } +} + +fn len_varint(len: usize) -> VarInt { + VarInt::from_u64(len as u64).expect("metadata length exceeds varint bounds") +} + +fn read_len(bytes: &mut &[u8]) -> Option { + let (len, rest) = VarInt::decode_bytes(bytes)?; + *bytes = rest; + usize::try_from(len.into_inner()).ok() +} + +fn write_varint(value: VarInt, out: &mut B) { + value.write_bytes(|bytes| out.put_slice(bytes)); +} From 1448caac21a039bbef46d86347f50e0694cfe23b Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Wed, 8 Jul 2026 19:08:11 -0400 Subject: [PATCH 07/13] ql-keyos: simplify RouteKey encodings --- Cargo.lock | 3 -- ql-api/Cargo.toml | 1 - ql-api/src/key.rs | 53 +++++++++++++++---------- ql-api/src/macros.rs | 4 +- ql-keyos/Cargo.toml | 2 - ql-keyos/src/lib.rs | 94 +++++++++++++++++++++++++------------------- 6 files changed, 89 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80af5db8..8ed367cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1372,7 +1372,6 @@ dependencies = [ "bytes", "ciborium", "flutter_rust_bridge", - "ql-common", "ql-keyos", "ql-rpc", "serde", @@ -1409,8 +1408,6 @@ dependencies = [ name = "ql-keyos" version = "0.1.0" dependencies = [ - "bytes", - "ql-common", "serde", ] diff --git a/ql-api/Cargo.toml b/ql-api/Cargo.toml index 3792167c..1509b0f5 100644 --- a/ql-api/Cargo.toml +++ b/ql-api/Cargo.toml @@ -7,7 +7,6 @@ license = "Proprietary" [dependencies] -ql-common = { workspace = true } ql-keyos = { workspace = true, features = ["serde"] } ql-rpc = { workspace = true } bytes = { workspace = true } diff --git a/ql-api/src/key.rs b/ql-api/src/key.rs index 2646dceb..26d49e8d 100644 --- a/ql-api/src/key.rs +++ b/ql-api/src/key.rs @@ -14,14 +14,14 @@ pub mod app_id { pub mod service_id { use ql_keyos::ServiceId; - pub const APP_STORE: ServiceId = ServiceId::from_u32(1); - pub const BACKUP: ServiceId = ServiceId::from_u32(2); - pub const BITCOIN: ServiceId = ServiceId::from_u32(3); - pub const DEBUG: ServiceId = ServiceId::from_u32(4); - pub const FIRMWARE: ServiceId = ServiceId::from_u32(5); - pub const FX: ServiceId = ServiceId::from_u32(6); - pub const SCV: ServiceId = ServiceId::from_u32(7); - pub const TIME: ServiceId = ServiceId::from_u32(8); + pub const APP_STORE: ServiceId = ServiceId(1); + pub const BACKUP: ServiceId = ServiceId(2); + pub const BITCOIN: ServiceId = ServiceId(3); + pub const DEBUG: ServiceId = ServiceId(4); + pub const FIRMWARE: ServiceId = ServiceId(5); + pub const FX: ServiceId = ServiceId(6); + pub const SCV: ServiceId = ServiceId(7); + pub const TIME: ServiceId = ServiceId(8); } /// Route key for a remote peer rpc @@ -33,18 +33,18 @@ pub struct ServiceRouteKey { impl ql_rpc::RpcRouteKey for ServiceRouteKey { fn encoded_len(&self) -> usize { - self.service_id.0.size() + self.route_id.0.size() + size_of::() + size_of::() } fn encode(&self, out: &mut W) { - self.service_id.0.write_bytes(|bytes| out.put_slice(bytes)); - self.route_id.0.write_bytes(|bytes| out.put_slice(bytes)); + out.put_u64(self.service_id.0); + out.put_u32(self.route_id.0); } - fn decode(bytes: &[u8]) -> Option { - let (service_id, bytes) = ql_common::VarInt::decode_bytes(bytes)?; - let (route_id, rest) = ql_common::VarInt::decode_bytes(bytes)?; - rest.is_empty().then_some(Self { + fn decode(mut bytes: &[u8]) -> Option { + let service_id = read_u64(&mut bytes)?; + let route_id = read_u32(&mut bytes)?; + bytes.is_empty().then_some(Self { service_id: ServiceId(service_id), route_id: RouteId(route_id), }) @@ -66,20 +66,33 @@ pub struct AppRouteKey { impl ql_rpc::RpcRouteKey for AppRouteKey { fn encoded_len(&self) -> usize { - AppId::SIZE + self.route_id.0.size() + AppId::SIZE + size_of::() } fn encode(&self, out: &mut W) { out.put_slice(&self.app_id.0); - self.route_id.0.write_bytes(|bytes| out.put_slice(bytes)); + out.put_u32(self.route_id.0); } - fn decode(bytes: &[u8]) -> Option { + fn decode(mut bytes: &[u8]) -> Option { let app_id = AppId(bytes.get(..AppId::SIZE)?.try_into().ok()?); - let (route_id, rest) = ql_common::VarInt::decode_bytes(&bytes[AppId::SIZE..])?; - rest.is_empty().then_some(Self { + bytes = &bytes[AppId::SIZE..]; + let route_id = read_u32(&mut bytes)?; + bytes.is_empty().then_some(Self { app_id, route_id: RouteId(route_id), }) } } + +fn read_u64(bytes: &mut &[u8]) -> Option { + let value = u64::from_be_bytes(bytes.get(..8)?.try_into().ok()?); + *bytes = &bytes[8..]; + Some(value) +} + +fn read_u32(bytes: &mut &[u8]) -> Option { + let value = u32::from_be_bytes(bytes.get(..4)?.try_into().ok()?); + *bytes = &bytes[4..]; + Some(value) +} diff --git a/ql-api/src/macros.rs b/ql-api/src/macros.rs index e700964f..3cff6896 100644 --- a/ql-api/src/macros.rs +++ b/ql-api/src/macros.rs @@ -56,7 +56,7 @@ macro_rules! service_routes { fn key() -> Self::Key { crate::ServiceRouteKey { service_id: $service_id, - route_id: ql_keyos::RouteId::from_u32($id), + route_id: ql_keyos::RouteId($id), } } } @@ -80,7 +80,7 @@ macro_rules! app_routes { fn key() -> Self::Key { crate::AppRouteKey { app_id: $app_id, - route_id: ql_keyos::RouteId::from_u32($id), + route_id: ql_keyos::RouteId($id), } } } diff --git a/ql-keyos/Cargo.toml b/ql-keyos/Cargo.toml index aa193c89..3be66926 100644 --- a/ql-keyos/Cargo.toml +++ b/ql-keyos/Cargo.toml @@ -6,8 +6,6 @@ description = "KeyOS QuantumLink addressing primitives" license = "Proprietary" [dependencies] -bytes = { workspace = true } -ql-common = { workspace = true } serde = { version = "1.0", features = ["derive"], optional = true } [features] diff --git a/ql-keyos/src/lib.rs b/ql-keyos/src/lib.rs index b1d8bb4c..f7a1c83c 100644 --- a/ql-keyos/src/lib.rs +++ b/ql-keyos/src/lib.rs @@ -1,8 +1,5 @@ //! KeyOS addressing primitives for QuantumLink -use bytes::BufMut; -pub use ql_common::VarInt; - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(transparent)] @@ -12,7 +9,7 @@ impl AppId { pub const SIZE: usize = 16; pub const fn from_hex(hex: &str) -> Self { - const fn hex_nibble(byte: u8) -> u8 { + const fn nibble(byte: u8) -> u8 { match byte { b'0'..=b'9' => byte - b'0', b'a'..=b'f' => byte - b'a' + 10, @@ -28,7 +25,7 @@ impl AppId { let mut app_id = [0; Self::SIZE]; let mut i = 0; while i < Self::SIZE { - app_id[i] = (hex_nibble(bytes[i * 2 + 2]) << 4) | hex_nibble(bytes[i * 2 + 3]); + app_id[i] = (nibble(bytes[i * 2 + 2]) << 4) | nibble(bytes[i * 2 + 3]); i += 1; } @@ -36,24 +33,7 @@ impl AppId { } } -ql_common::varint_wrapper!( - /// Identifier for a route within a KeyOS app or service namespace - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "serde", serde(into = "u64", try_from = "u64"))] - RouteId -); - -ql_common::varint_wrapper!( - /// Identifier for a KeyOS service advertised by a peer - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "serde", serde(into = "u64", try_from = "u64"))] - ServiceId -); - -pub trait ServiceTargetKey { - fn service_id(&self) -> ServiceId; -} - +// TODO: version #[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PeerMetadata { @@ -64,19 +44,32 @@ pub struct PeerMetadata { } impl PeerMetadata { - pub fn write_bytes(&self, out: &mut B) { - write_varint(len_varint(self.app_ids.len()), out); + pub fn write_bytes(&self, out: &mut Vec) { + fn len_u64(len: usize) -> u64 { + len.try_into().expect("metadata length exceeds u64") + } + + out.extend_from_slice(&len_u64(self.app_ids.len()).to_be_bytes()); for app_id in &self.app_ids { - out.put_slice(&app_id.0); + out.extend_from_slice(&app_id.0); } - write_varint(len_varint(self.service_ids.len()), out); + out.extend_from_slice(&len_u64(self.service_ids.len()).to_be_bytes()); for service_id in &self.service_ids { - write_varint(service_id.0, out); + out.extend_from_slice(&service_id.0.to_be_bytes()); } } pub fn from_bytes(mut bytes: &[u8]) -> Option { + fn read_len(bytes: &mut &[u8]) -> Option { + read_u64(bytes)?.try_into().ok() + } + fn read_u64(bytes: &mut &[u8]) -> Option { + let value = u64::from_be_bytes(bytes.get(..8)?.try_into().ok()?); + *bytes = &bytes[8..]; + Some(value) + } + let app_count = read_len(&mut bytes)?; let app_bytes_len = app_count.checked_mul(AppId::SIZE)?; if app_bytes_len > bytes.len() { @@ -95,9 +88,7 @@ impl PeerMetadata { } let mut service_ids = Vec::with_capacity(service_count); for _ in 0..service_count { - let (service_id, rest) = VarInt::decode_bytes(bytes)?; - bytes = rest; - service_ids.push(ServiceId(service_id)); + service_ids.push(ServiceId(read_u64(&mut bytes)?)); } bytes.is_empty().then_some(Self { @@ -105,18 +96,41 @@ impl PeerMetadata { service_ids, }) } -} -fn len_varint(len: usize) -> VarInt { - VarInt::from_u64(len as u64).expect("metadata length exceeds varint bounds") + pub fn to_vec(&self) -> Vec { + let mut bytes = Vec::default(); + self.write_bytes(&mut bytes); + bytes + } } -fn read_len(bytes: &mut &[u8]) -> Option { - let (len, rest) = VarInt::decode_bytes(bytes)?; - *bytes = rest; - usize::try_from(len.into_inner()).ok() +macro_rules! wrapper { + ($(#[$attr:meta])* $name:ident, $ty:ty) => { + $(#[$attr])* + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[repr(transparent)] + pub struct $name(pub $ty); + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + }; } -fn write_varint(value: VarInt, out: &mut B) { - value.write_bytes(|bytes| out.put_slice(bytes)); +wrapper!( + /// Identifier for a route within a KeyOS app or service namespace + RouteId, + u32 +); +wrapper!( + /// Identifier for a KeyOS service advertised by a peer + ServiceId, + u64 +); + +pub trait ServiceTargetKey { + fn service_id(&self) -> ServiceId; } From c074f0e20fb2d8e24bdb03fc7a47318cb6f69a6c Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Thu, 9 Jul 2026 08:33:45 -0400 Subject: [PATCH 08/13] ql-api: remove subscription bytes benchmark --- ql-api/src/debug.rs | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/ql-api/src/debug.rs b/ql-api/src/debug.rs index 93e35eb9..276a6e96 100644 --- a/ql-api/src/debug.rs +++ b/ql-api/src/debug.rs @@ -1,4 +1,4 @@ -use ql_rpc::{request::Request, Download, Subscription}; +use ql_rpc::{request::Request, Download}; use crate::Error; @@ -7,8 +7,7 @@ use crate::Error; app_routes! { crate::app_id::DEBUG => { RequestPassportEcho: Request = 1, - SubscribePassportBytesBenchmark: Subscription = 2, - DownloadPassportBenchmark: Download = 3, + DownloadPassportBenchmark: Download = 2, } } @@ -24,18 +23,6 @@ rpc! { } } -rpc! { - pub struct BytesBenchmarkParams { - pub length: u32, - } -} - -rpc! { - pub struct BytesBenchmarkEvent { - pub bytes: Vec, - } -} - rpc! { pub struct DownloadBenchmarkParams { pub length: u64, @@ -58,12 +45,6 @@ impl Request for RequestPassportEcho { type Response = EchoResponse; } -impl Subscription for SubscribePassportBytesBenchmark { - type Error = Error; - type Event = BytesBenchmarkEvent; - type Request = BytesBenchmarkParams; -} - impl Download for DownloadPassportBenchmark { type Error = Error; type Request = DownloadBenchmarkParams; @@ -76,7 +57,6 @@ impl Download for DownloadPassportBenchmark { service_routes! { crate::service_id::DEBUG => { RequestEcho: Request = 1, - SubscribeBytesBenchmark: Subscription = 2, DownloadBenchmark: Download = 3, } } @@ -87,12 +67,6 @@ impl Request for RequestEcho { type Response = EchoResponse; } -impl Subscription for SubscribeBytesBenchmark { - type Error = Error; - type Event = BytesBenchmarkEvent; - type Request = BytesBenchmarkParams; -} - impl Download for DownloadBenchmark { type Error = Error; type Request = DownloadBenchmarkParams; From a9c55a99e645419603676c176d150b7f8761b4f2 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Fri, 10 Jul 2026 09:03:18 -0400 Subject: [PATCH 09/13] ql-api/keyos: use codec + system permissioning --- Cargo.lock | 3 + ql-api/Cargo.toml | 1 + ql-api/src/key.rs | 56 +++++++---------- ql-api/src/lib.rs | 4 +- ql-api/src/{settings.rs => system.rs} | 20 ++++++- ql-keyos/Cargo.toml | 2 + ql-keyos/src/lib.rs | 86 +++++++-------------------- 7 files changed, 72 insertions(+), 100 deletions(-) rename ql-api/src/{settings.rs => system.rs} (82%) diff --git a/Cargo.lock b/Cargo.lock index 8ed367cb..7b08839e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1372,6 +1372,7 @@ dependencies = [ "bytes", "ciborium", "flutter_rust_bridge", + "ql-codec", "ql-keyos", "ql-rpc", "serde", @@ -1408,6 +1409,8 @@ dependencies = [ name = "ql-keyos" version = "0.1.0" dependencies = [ + "bytes", + "ql-codec", "serde", ] diff --git a/ql-api/Cargo.toml b/ql-api/Cargo.toml index 1509b0f5..27f93d57 100644 --- a/ql-api/Cargo.toml +++ b/ql-api/Cargo.toml @@ -7,6 +7,7 @@ license = "Proprietary" [dependencies] +ql-codec = { workspace = true } ql-keyos = { workspace = true, features = ["serde"] } ql-rpc = { workspace = true } bytes = { workspace = true } diff --git a/ql-api/src/key.rs b/ql-api/src/key.rs index 26d49e8d..46c750e9 100644 --- a/ql-api/src/key.rs +++ b/ql-api/src/key.rs @@ -1,9 +1,10 @@ +use ql_codec::{Encode, Reader}; use ql_keyos::{AppId, RouteId, ServiceId}; pub mod app_id { use ql_keyos::AppId; - pub const SYSTEM: AppId = AppId::from_hex("0x6f732f716c0000000000000000000000"); + pub const SYSTEM: AppId = AppId([0; AppId::SIZE]); pub const ONBOARDING: AppId = AppId::from_hex("0xdac5321775d449c11bc9c90f38067f8f"); pub const BACKUP: AppId = AppId::from_hex("0xe19f16fcd9610bca7d026b4673f1cb06"); pub const UPDATE: AppId = AppId::from_hex("0x6F732F75706461746500000000000000"); @@ -33,21 +34,21 @@ pub struct ServiceRouteKey { impl ql_rpc::RpcRouteKey for ServiceRouteKey { fn encoded_len(&self) -> usize { - size_of::() + size_of::() + self.service_id.encoded_len() + self.route_id.encoded_len() } fn encode(&self, out: &mut W) { - out.put_u64(self.service_id.0); - out.put_u32(self.route_id.0); + self.service_id.encode(out); + self.route_id.encode(out); } - fn decode(mut bytes: &[u8]) -> Option { - let service_id = read_u64(&mut bytes)?; - let route_id = read_u32(&mut bytes)?; - bytes.is_empty().then_some(Self { - service_id: ServiceId(service_id), - route_id: RouteId(route_id), - }) + fn decode(bytes: &[u8]) -> Option { + let mut reader = Reader::new(bytes); + let key = Self { + service_id: reader.decode().ok()?, + route_id: reader.decode().ok()?, + }; + reader.is_empty().then_some(key) } } @@ -66,33 +67,20 @@ pub struct AppRouteKey { impl ql_rpc::RpcRouteKey for AppRouteKey { fn encoded_len(&self) -> usize { - AppId::SIZE + size_of::() + self.app_id.encoded_len() + self.route_id.encoded_len() } fn encode(&self, out: &mut W) { - out.put_slice(&self.app_id.0); - out.put_u32(self.route_id.0); + self.app_id.encode(out); + self.route_id.encode(out); } - fn decode(mut bytes: &[u8]) -> Option { - let app_id = AppId(bytes.get(..AppId::SIZE)?.try_into().ok()?); - bytes = &bytes[AppId::SIZE..]; - let route_id = read_u32(&mut bytes)?; - bytes.is_empty().then_some(Self { - app_id, - route_id: RouteId(route_id), - }) + fn decode(bytes: &[u8]) -> Option { + let mut reader = Reader::new(bytes); + let key = Self { + app_id: reader.decode().ok()?, + route_id: reader.decode().ok()?, + }; + reader.is_empty().then_some(key) } } - -fn read_u64(bytes: &mut &[u8]) -> Option { - let value = u64::from_be_bytes(bytes.get(..8)?.try_into().ok()?); - *bytes = &bytes[8..]; - Some(value) -} - -fn read_u32(bytes: &mut &[u8]) -> Option { - let value = u32::from_be_bytes(bytes.get(..4)?.try_into().ok()?); - *bytes = &bytes[4..]; - Some(value) -} diff --git a/ql-api/src/lib.rs b/ql-api/src/lib.rs index 839676d9..47c1f1df 100644 --- a/ql-api/src/lib.rs +++ b/ql-api/src/lib.rs @@ -11,12 +11,12 @@ pub mod fx; pub mod key; pub mod onboarding; pub mod scv; -pub mod settings; +pub mod system; pub mod time; pub use self::{ app_store::*, backup::*, bitcoin::*, codec::Empty, debug::*, firmware::*, fx::*, key::*, - onboarding::*, scv::*, settings::*, time::*, + onboarding::*, scv::*, system::*, time::*, }; pub type Error = ciborium::de::Error; diff --git a/ql-api/src/settings.rs b/ql-api/src/system.rs similarity index 82% rename from ql-api/src/settings.rs rename to ql-api/src/system.rs index c09e452d..fc79e765 100644 --- a/ql-api/src/settings.rs +++ b/ql-api/src/system.rs @@ -1,4 +1,4 @@ -use ql_keyos::AppId; +use ql_keyos::{AppId, PeerPermissions}; use ql_rpc::{Request, Subscription}; use crate::{Empty, Error}; @@ -37,6 +37,7 @@ app_routes! { RequestPassportFiatPreference: Request = 3, SubscribePassportFiatPreference: Subscription = 4, SubscribeAppActivity: Subscription = 5, + RequestUpdatePeerPermissions: Request = 6, } } @@ -100,4 +101,21 @@ impl Subscription for SubscribeAppActivity { type Event = AppActivityEvent; } +rpc! { + pub struct UpdatePeerPermissionsParams(pub PeerPermissions); +} + +rpc! { + pub enum UpdatePeerPermissionsResponse { + Updated, + Denied, + } +} + +impl Request for RequestUpdatePeerPermissions { + type Error = Error; + type Request = UpdatePeerPermissionsParams; + type Response = UpdatePeerPermissionsResponse; +} + // SERVICE ROUTES diff --git a/ql-keyos/Cargo.toml b/ql-keyos/Cargo.toml index 3be66926..123d9c94 100644 --- a/ql-keyos/Cargo.toml +++ b/ql-keyos/Cargo.toml @@ -6,6 +6,8 @@ description = "KeyOS QuantumLink addressing primitives" license = "Proprietary" [dependencies] +bytes = { workspace = true } +ql-codec = { workspace = true } serde = { version = "1.0", features = ["derive"], optional = true } [features] diff --git a/ql-keyos/src/lib.rs b/ql-keyos/src/lib.rs index f7a1c83c..bb281c34 100644 --- a/ql-keyos/src/lib.rs +++ b/ql-keyos/src/lib.rs @@ -1,5 +1,7 @@ //! KeyOS addressing primitives for QuantumLink +use ql_codec::{ByteSlice, Decode, Encode, Reader}; + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(transparent)] @@ -33,77 +35,32 @@ impl AppId { } } -// TODO: version +impl Encode for AppId { + fn encoded_len(&self) -> usize { + Self::SIZE + } + + fn encode(&self, out: &mut W) { + self.0.encode(out); + } +} + +impl Decode for AppId { + fn decode(reader: &mut Reader) -> Result { + Ok(Self(reader.decode()?)) + } +} + +// todo: version #[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct PeerMetadata { +pub struct PeerPermissions { /// KeyOS app ids this peer is allowed to open streams toward pub app_ids: Vec, /// Service ids this peer advertises for KeyOS-opened streams pub service_ids: Vec, } -impl PeerMetadata { - pub fn write_bytes(&self, out: &mut Vec) { - fn len_u64(len: usize) -> u64 { - len.try_into().expect("metadata length exceeds u64") - } - - out.extend_from_slice(&len_u64(self.app_ids.len()).to_be_bytes()); - for app_id in &self.app_ids { - out.extend_from_slice(&app_id.0); - } - - out.extend_from_slice(&len_u64(self.service_ids.len()).to_be_bytes()); - for service_id in &self.service_ids { - out.extend_from_slice(&service_id.0.to_be_bytes()); - } - } - - pub fn from_bytes(mut bytes: &[u8]) -> Option { - fn read_len(bytes: &mut &[u8]) -> Option { - read_u64(bytes)?.try_into().ok() - } - fn read_u64(bytes: &mut &[u8]) -> Option { - let value = u64::from_be_bytes(bytes.get(..8)?.try_into().ok()?); - *bytes = &bytes[8..]; - Some(value) - } - - let app_count = read_len(&mut bytes)?; - let app_bytes_len = app_count.checked_mul(AppId::SIZE)?; - if app_bytes_len > bytes.len() { - return None; - } - let mut app_ids = Vec::with_capacity(app_count); - for _ in 0..app_count { - let app_id = AppId(bytes.get(..AppId::SIZE)?.try_into().ok()?); - bytes = &bytes[AppId::SIZE..]; - app_ids.push(app_id); - } - - let service_count = read_len(&mut bytes)?; - if service_count > bytes.len() { - return None; - } - let mut service_ids = Vec::with_capacity(service_count); - for _ in 0..service_count { - service_ids.push(ServiceId(read_u64(&mut bytes)?)); - } - - bytes.is_empty().then_some(Self { - app_ids, - service_ids, - }) - } - - pub fn to_vec(&self) -> Vec { - let mut bytes = Vec::default(); - self.write_bytes(&mut bytes); - bytes - } -} - macro_rules! wrapper { ($(#[$attr:meta])* $name:ident, $ty:ty) => { $(#[$attr])* @@ -131,6 +88,9 @@ wrapper!( u64 ); +ql_codec::varint_wrapper!(RouteId, u32); +ql_codec::varint_wrapper!(ServiceId, u64); + pub trait ServiceTargetKey { fn service_id(&self) -> ServiceId; } From 284c9685b8c79998b81062dd40c7ce2895408b78 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Fri, 10 Jul 2026 16:09:19 -0400 Subject: [PATCH 10/13] ql-keyos: move route keys --- Cargo.lock | 1 + ql-api/src/key.rs | 63 +------------------------------------------- ql-api/src/macros.rs | 8 +++--- ql-keyos/Cargo.toml | 1 + ql-keyos/src/lib.rs | 52 ++++++++++++++++++++++++++++++++++-- 5 files changed, 57 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b08839e..2681b91c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1411,6 +1411,7 @@ version = "0.1.0" dependencies = [ "bytes", "ql-codec", + "ql-rpc", "serde", ] diff --git a/ql-api/src/key.rs b/ql-api/src/key.rs index 46c750e9..822ce202 100644 --- a/ql-api/src/key.rs +++ b/ql-api/src/key.rs @@ -1,5 +1,4 @@ -use ql_codec::{Encode, Reader}; -use ql_keyos::{AppId, RouteId, ServiceId}; +pub use ql_keyos::{AppRouteKey, ServiceRouteKey}; pub mod app_id { use ql_keyos::AppId; @@ -24,63 +23,3 @@ pub mod service_id { pub const SCV: ServiceId = ServiceId(7); pub const TIME: ServiceId = ServiceId(8); } - -/// Route key for a remote peer rpc -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ServiceRouteKey { - pub service_id: ServiceId, - pub route_id: RouteId, -} - -impl ql_rpc::RpcRouteKey for ServiceRouteKey { - fn encoded_len(&self) -> usize { - self.service_id.encoded_len() + self.route_id.encoded_len() - } - - fn encode(&self, out: &mut W) { - self.service_id.encode(out); - self.route_id.encode(out); - } - - fn decode(bytes: &[u8]) -> Option { - let mut reader = Reader::new(bytes); - let key = Self { - service_id: reader.decode().ok()?, - route_id: reader.decode().ok()?, - }; - reader.is_empty().then_some(key) - } -} - -impl ql_keyos::ServiceTargetKey for ServiceRouteKey { - fn service_id(&self) -> ServiceId { - self.service_id - } -} - -/// Route key for an keyos handled rpc -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct AppRouteKey { - pub app_id: AppId, - pub route_id: RouteId, -} - -impl ql_rpc::RpcRouteKey for AppRouteKey { - fn encoded_len(&self) -> usize { - self.app_id.encoded_len() + self.route_id.encoded_len() - } - - fn encode(&self, out: &mut W) { - self.app_id.encode(out); - self.route_id.encode(out); - } - - fn decode(bytes: &[u8]) -> Option { - let mut reader = Reader::new(bytes); - let key = Self { - app_id: reader.decode().ok()?, - route_id: reader.decode().ok()?, - }; - reader.is_empty().then_some(key) - } -} diff --git a/ql-api/src/macros.rs b/ql-api/src/macros.rs index 3cff6896..539a4e35 100644 --- a/ql-api/src/macros.rs +++ b/ql-api/src/macros.rs @@ -51,10 +51,10 @@ macro_rules! service_routes { pub struct $route; impl ql_rpc::Route for $route { - type Key = crate::ServiceRouteKey; + type Key = ql_keyos::ServiceRouteKey; fn key() -> Self::Key { - crate::ServiceRouteKey { + ql_keyos::ServiceRouteKey { service_id: $service_id, route_id: ql_keyos::RouteId($id), } @@ -75,10 +75,10 @@ macro_rules! app_routes { pub struct $route; impl ql_rpc::Route for $route { - type Key = crate::AppRouteKey; + type Key = ql_keyos::AppRouteKey; fn key() -> Self::Key { - crate::AppRouteKey { + ql_keyos::AppRouteKey { app_id: $app_id, route_id: ql_keyos::RouteId($id), } diff --git a/ql-keyos/Cargo.toml b/ql-keyos/Cargo.toml index 123d9c94..caa16da1 100644 --- a/ql-keyos/Cargo.toml +++ b/ql-keyos/Cargo.toml @@ -8,6 +8,7 @@ license = "Proprietary" [dependencies] bytes = { workspace = true } ql-codec = { workspace = true } +ql-rpc = { workspace = true } serde = { version = "1.0", features = ["derive"], optional = true } [features] diff --git a/ql-keyos/src/lib.rs b/ql-keyos/src/lib.rs index bb281c34..665d31a6 100644 --- a/ql-keyos/src/lib.rs +++ b/ql-keyos/src/lib.rs @@ -91,6 +91,54 @@ wrapper!( ql_codec::varint_wrapper!(RouteId, u32); ql_codec::varint_wrapper!(ServiceId, u64); -pub trait ServiceTargetKey { - fn service_id(&self) -> ServiceId; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ServiceRouteKey { + pub service_id: ServiceId, + pub route_id: RouteId, +} + +impl ql_rpc::RpcRouteKey for ServiceRouteKey { + fn encoded_len(&self) -> usize { + self.service_id.encoded_len() + self.route_id.encoded_len() + } + + fn encode(&self, out: &mut W) { + self.service_id.encode(out); + self.route_id.encode(out); + } + + fn decode(bytes: &[u8]) -> Option { + let mut reader = Reader::new(bytes); + let key = Self { + service_id: reader.decode().ok()?, + route_id: reader.decode().ok()?, + }; + reader.is_empty().then_some(key) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AppRouteKey { + pub app_id: AppId, + pub route_id: RouteId, +} + +impl ql_rpc::RpcRouteKey for AppRouteKey { + fn encoded_len(&self) -> usize { + self.app_id.encoded_len() + self.route_id.encoded_len() + } + + fn encode(&self, out: &mut W) { + self.app_id.encode(out); + self.route_id.encode(out); + } + + fn decode(bytes: &[u8]) -> Option { + let mut reader = Reader::new(bytes); + let key = Self { + app_id: reader.decode().ok()?, + route_id: reader.decode().ok()?, + }; + reader.is_empty().then_some(key) + } } From 12ca6eea5aa76496323c3932fcc764f0cf2b1139 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 14 Jul 2026 10:02:16 -0400 Subject: [PATCH 11/13] ql-api: RequestPeerPermissions --- ql-api/src/system.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ql-api/src/system.rs b/ql-api/src/system.rs index fc79e765..e327d7da 100644 --- a/ql-api/src/system.rs +++ b/ql-api/src/system.rs @@ -37,7 +37,7 @@ app_routes! { RequestPassportFiatPreference: Request = 3, SubscribePassportFiatPreference: Subscription = 4, SubscribeAppActivity: Subscription = 5, - RequestUpdatePeerPermissions: Request = 6, + RequestPeerPermissions: Request = 6, } } @@ -102,20 +102,20 @@ impl Subscription for SubscribeAppActivity { } rpc! { - pub struct UpdatePeerPermissionsParams(pub PeerPermissions); + pub struct PeerPermissionsParams(pub PeerPermissions); } rpc! { - pub enum UpdatePeerPermissionsResponse { + pub enum PeerPermissionsResponse { Updated, Denied, } } -impl Request for RequestUpdatePeerPermissions { +impl Request for RequestPeerPermissions { type Error = Error; - type Request = UpdatePeerPermissionsParams; - type Response = UpdatePeerPermissionsResponse; + type Request = PeerPermissionsParams; + type Response = PeerPermissionsResponse; } // SERVICE ROUTES From 5d34b3ca7dd83292c7b37af297bd207abbcb0b49 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 21 Jul 2026 11:05:33 -0400 Subject: [PATCH 12/13] remove service ids from peer permissions --- ql-keyos/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ql-keyos/src/lib.rs b/ql-keyos/src/lib.rs index 665d31a6..4601d46b 100644 --- a/ql-keyos/src/lib.rs +++ b/ql-keyos/src/lib.rs @@ -57,8 +57,6 @@ impl Decode for AppId { pub struct PeerPermissions { /// KeyOS app ids this peer is allowed to open streams toward pub app_ids: Vec, - /// Service ids this peer advertises for KeyOS-opened streams - pub service_ids: Vec, } macro_rules! wrapper { From a2f088173cecf310019c19286a855d36326cfcb3 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 21 Jul 2026 09:46:16 -0400 Subject: [PATCH 13/13] ql-api: bootstrap peer bundles --- ql-api/src/system.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ql-api/src/system.rs b/ql-api/src/system.rs index e327d7da..780751b9 100644 --- a/ql-api/src/system.rs +++ b/ql-api/src/system.rs @@ -38,6 +38,7 @@ app_routes! { SubscribePassportFiatPreference: Subscription = 4, SubscribeAppActivity: Subscription = 5, RequestPeerPermissions: Request = 6, + RequestInstallPeerBundles: Request = 7, } } @@ -118,4 +119,24 @@ impl Request for RequestPeerPermissions { type Response = PeerPermissionsResponse; } +rpc! { + pub struct InstallPeerBundlesParams { + pub router: Vec, + pub peers: Vec>, + } +} + +rpc! { + pub enum InstallPeerBundlesResponse { + Installed, + Rejected, + } +} + +impl Request for RequestInstallPeerBundles { + type Error = Error; + type Request = InstallPeerBundlesParams; + type Response = InstallPeerBundlesResponse; +} + // SERVICE ROUTES