From 90daccea46877d150fececb2dd65fa60175b041f Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 5 Dec 2023 13:19:49 +1100 Subject: [PATCH 01/49] merge main into sigchat --- Cargo.lock | 528 +++++++++++------- Cargo.toml | 5 +- LICENSE | 202 +++++++ LICENSES/Apache-2.0.txt | 203 +------ RELEASE-v0.9.md | 8 + api/xous-api-log/Cargo.toml | 7 +- api/xous-api-log/src/lib.rs | 6 +- api/xous-api-names/Cargo.toml | 8 +- api/xous-api-susres/Cargo.toml | 10 +- api/xous-api-ticktimer/Cargo.toml | 12 +- apps/app-loader/Cargo.toml | 27 + apps/app-loader/README.md | 47 ++ apps/app-loader/locales/i18n.json | 50 ++ apps/app-loader/spawn/Cargo.toml | 11 + apps/app-loader/spawn/README.md | 24 + apps/app-loader/spawn/build.rs | 33 ++ apps/app-loader/spawn/link.x | 190 +++++++ apps/app-loader/spawn/src/main.rs | 190 +++++++ apps/app-loader/src/api.rs | 448 +++++++++++++++ apps/app-loader/src/main.rs | 45 ++ apps/app-loader/src/spawn.bin | Bin 0 -> 21452 bytes apps/ball/Cargo.toml | 10 +- apps/chat-test/Cargo.toml | 42 ++ apps/chat-test/README.md | 5 + apps/chat-test/src/api.rs | 32 ++ apps/chat-test/src/main.rs | 112 ++++ apps/chat-test/src/tests.rs | 30 + apps/hello/Cargo.toml | 8 +- apps/manifest.json | 19 + apps/mtxchat/Cargo.toml | 11 +- apps/mtxchat/locales/i18n.json | 38 +- apps/mtxchat/src/api.rs | 2 +- apps/mtxchat/src/lib.rs | 58 +- apps/mtxchat/src/listen.rs | 25 +- apps/mtxchat/src/main.rs | 1 + apps/mtxcli/Cargo.toml | 10 +- apps/mtxcli/locales/i18n.json | 118 ++-- apps/repl/Cargo.toml | 10 +- apps/transientdisk/Cargo.toml | 8 +- apps/vault/Cargo.toml | 14 +- apps/vault/libraries/crypto/Cargo.toml | 2 +- apps/vault/locales/i18n.json | 7 + apps/vault/src/actions.rs | 76 ++- apps/vault/src/env/xous/mod.rs | 8 +- apps/vault/src/main.rs | 6 +- imports/getrandom/Cargo.toml | 6 +- kernel/Cargo.toml | 6 +- kernel/src/arch/riscv/process.rs | 1 + libs/chat/Cargo.toml | 10 +- libs/chat/locales/i18n.json | 4 + libs/chat/src/api.rs | 22 + libs/chat/src/dialogue.rs | 35 +- libs/chat/src/dialogue/post.rs | 3 + libs/chat/src/lib.rs | 282 +++++++++- libs/chat/src/main.rs | 16 +- libs/chat/src/ui.rs | 524 ++++++++++------- .../usbd_bulk_only_transport/Cargo.toml | 4 +- libs/mass-storage/usbd_scsi/Cargo.toml | 4 +- libs/tls/Cargo.toml | 10 +- libs/tls/README.md | 6 +- libs/tls/locales/i18n.json | 16 +- libs/tls/src/cmd.rs | 3 +- libs/tls/src/danger.rs | 29 +- libs/tls/src/lib.rs | 5 +- libs/userprefs/Cargo.toml | 2 +- libs/xous-pio/Cargo.toml | 2 +- libs/xous-pl230/Cargo.toml | 2 +- services/aes-test/Cargo.toml | 4 +- services/codec/Cargo.toml | 12 +- services/com/Cargo.toml | 12 +- services/content-plugin-api/Cargo.toml | 4 +- services/cram-console/Cargo.toml | 8 +- services/cram-mbox1/Cargo.toml | 8 +- services/cram-mbox2/Cargo.toml | 8 +- services/dns/Cargo.toml | 12 +- services/early_settings/Cargo.toml | 12 +- services/engine-25519/Cargo.toml | 12 +- services/engine-sha512/Cargo.toml | 12 +- services/ffi-test/Cargo.toml | 10 +- services/gam/Cargo.toml | 13 +- services/gam/src/api.rs | 10 + services/gam/src/contexts.rs | 4 + services/gam/src/lib.rs | 12 + services/gam/src/main.rs | 14 +- services/gam/src/tokens.rs | 31 +- services/graphics-server/Cargo.toml | 12 +- services/graphics-server/src/api/text.rs | 7 +- services/graphics-server/src/lib.rs | 1 + services/graphics-server/src/main.rs | 85 ++- services/graphics-server/src/op.rs | 24 +- services/graphics-server/src/testing.rs | 44 +- services/ime-frontend/Cargo.toml | 10 +- services/ime-plugin-api/Cargo.toml | 6 +- services/ime-plugin-shell/Cargo.toml | 10 +- services/ime-plugin-tts/Cargo.toml | 10 +- services/jtag/Cargo.toml | 12 +- services/jtag/src/efuse_api.rs | 8 +- services/kernel-test/Cargo.toml | 6 +- services/keyboard/Cargo.toml | 12 +- services/libstd-test/Cargo.toml | 8 +- services/llio/Cargo.toml | 12 +- services/log-test-client/Cargo.toml | 2 +- services/modals/Cargo.toml | 10 +- services/net/Cargo.toml | 12 +- services/net/src/tests.rs | 2 +- services/pddb/Cargo.toml | 12 +- services/pddb/src/lib.rs | 2 +- services/root-keys/Cargo.toml | 12 +- services/shellchat/Cargo.toml | 12 +- services/shellchat/src/nettests.rs | 2 +- services/skeleton/Cargo.toml | 10 +- services/spinor/Cargo.toml | 12 +- services/status/Cargo.toml | 12 +- services/test-spawn/Cargo.toml | 2 +- services/test-spawn/spawn/Cargo.toml | 2 +- services/ticktimer-test-client/Cargo.toml | 4 +- services/trng/Cargo.toml | 12 +- services/tts/Cargo.toml | 10 +- services/usb-device-xous/Cargo.toml | 18 +- services/usb-device-xous/src/lib.rs | 16 +- services/usb-device-xous/src/main_hosted.rs | 4 +- services/usb-device-xous/src/main_hw.rs | 99 ++-- services/usb-device-xous/src/mappings.rs | 2 +- services/usb-test/Cargo.toml | 12 +- services/usb-test/src/main.rs | 12 +- services/xous-log/Cargo.toml | 8 +- services/xous-names/Cargo.toml | 8 +- services/xous-susres/Cargo.toml | 10 +- services/xous-ticktimer/Cargo.toml | 12 +- tools/app_server.py | 70 +++ tools/perflib/Cargo.toml | 2 +- xous-ipc/Cargo.toml | 4 +- xous-rs/Cargo.toml | 2 +- xous-rs/build.rs | 3 - xous-rs/src/arch/hosted/mod.rs | 6 +- xous-rs/src/arch/hosted/process.rs | 4 +- xous-rs/src/definitions.rs | 6 +- xous-rs/src/definitions/messages/envelope.rs | 8 +- xous-rs/src/definitions/messages/mod.rs | 2 +- xous-rs/src/syscall.rs | 34 +- xtask/src/app_manifest.rs | 12 +- xtask/src/builder.rs | 20 +- xtask/src/main.rs | 7 +- xtask/src/verifier.rs | 12 +- 144 files changed, 3475 insertions(+), 1211 deletions(-) create mode 100644 LICENSE mode change 100644 => 120000 LICENSES/Apache-2.0.txt create mode 100644 apps/app-loader/Cargo.toml create mode 100644 apps/app-loader/README.md create mode 100644 apps/app-loader/locales/i18n.json create mode 100644 apps/app-loader/spawn/Cargo.toml create mode 100644 apps/app-loader/spawn/README.md create mode 100644 apps/app-loader/spawn/build.rs create mode 100644 apps/app-loader/spawn/link.x create mode 100644 apps/app-loader/spawn/src/main.rs create mode 100644 apps/app-loader/src/api.rs create mode 100644 apps/app-loader/src/main.rs create mode 100644 apps/app-loader/src/spawn.bin create mode 100644 apps/chat-test/Cargo.toml create mode 100644 apps/chat-test/README.md create mode 100644 apps/chat-test/src/api.rs create mode 100644 apps/chat-test/src/main.rs create mode 100644 apps/chat-test/src/tests.rs create mode 100644 tools/app_server.py delete mode 100644 xous-rs/build.rs diff --git a/Cargo.lock b/Cargo.lock index 9ca5d5c2a..b5e0ead22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,7 +74,7 @@ dependencies = [ "aes 0.8.1", "hex-literal", "log", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", ] @@ -102,6 +102,27 @@ version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +[[package]] +name = "app-loader" +version = "0.1.0" +dependencies = [ + "gam", + "graphics-server", + "locales", + "log", + "modals", + "num-derive", + "num-traits", + "rkyv", + "ureq", + "url", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-api-log", + "xous-api-names", + "xous-api-ticktimer", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "argh" version = "0.1.8" @@ -264,11 +285,11 @@ dependencies = [ "num-derive", "num-traits", "trng", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -586,11 +607,46 @@ dependencies = [ "pddb", "rkyv", "trng", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "chat-test" +version = "0.1.0" +dependencies = [ + "chat", + "codec", + "com", + "com_rs 0.1.0 (git+https://github.com/betrusted-io/com_rs?rev=891bdd3ca8e41f81510d112483e178aea3e3a921)", + "content-plugin-api", + "gam", + "graphics-server", + "ime-plugin-api", + "ime-plugin-shell", + "llio", + "locales", + "log", + "modals", + "net", + "num-derive", + "num-traits", + "pddb", + "percent-encoding", + "rkyv", + "serde", + "tls", + "trng", + "ureq", + "url", + "xous 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-api-log", + "xous-api-names", + "xous-api-ticktimer", + "xous-ipc 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -705,12 +761,12 @@ dependencies = [ "rkyv", "trng", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -736,12 +792,12 @@ dependencies = [ "trng", "typenum", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-semver", ] @@ -793,7 +849,7 @@ dependencies = [ "graphics-server", "log", "rkyv", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-names", ] @@ -834,7 +890,7 @@ version = "0.1.0" dependencies = [ "log", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", @@ -850,7 +906,7 @@ dependencies = [ "num-derive", "num-traits", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", @@ -864,7 +920,7 @@ dependencies = [ "num-derive", "num-traits", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", @@ -1287,28 +1343,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "delegate" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c47a31748d9cfa641f6cccb3608385fafe261ba36054f3d40d5a3ca11eb1af" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "delegate" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70a2d4995466955a415223acf3c9c934b9ff2339631cdf4ffc893da4bacd717" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - [[package]] name = "der" version = "0.5.1" @@ -1439,12 +1473,12 @@ dependencies = [ "trng", "userprefs", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1464,12 +1498,12 @@ dependencies = [ "rkyv", "spinor", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1619,12 +1653,12 @@ dependencies = [ "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1778,7 +1812,7 @@ dependencies = [ "num-derive", "num-traits", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", @@ -1836,11 +1870,10 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ - "matches", "percent-encoding", ] @@ -1883,6 +1916,15 @@ dependencies = [ "syn 1.0.103", ] +[[package]] +name = "fugit" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" +dependencies = [ + "gcd", +] + [[package]] name = "funty" version = "1.2.0" @@ -2002,14 +2044,20 @@ dependencies = [ "trng", "tts-frontend", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + [[package]] name = "gdbstub" version = "0.6.5" @@ -2068,9 +2116,9 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", "wasm-bindgen-test", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-names", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2103,12 +2151,12 @@ dependencies = [ "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2138,14 +2186,12 @@ dependencies = [ ] [[package]] -name = "hash32-derive" -version = "0.1.1" +name = "hash32" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59d2aba832b60be25c1b169146b27c64115470981b128ed84c8db18c1b03c6ff" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", + "byteorder", ] [[package]] @@ -2161,12 +2207,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" dependencies = [ "atomic-polyfill", - "hash32", + "hash32 0.2.1", "rustc_version 0.4.0", "spin 0.9.3", "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.3.3" @@ -2193,10 +2249,10 @@ dependencies = [ "num-derive", "num-traits", "tts-frontend", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2312,11 +2368,10 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.2.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -2336,11 +2391,11 @@ dependencies = [ "rkyv", "tts-frontend", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2352,9 +2407,9 @@ dependencies = [ "num-derive", "num-traits", "rkyv", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-names", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2367,11 +2422,11 @@ dependencies = [ "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2386,11 +2441,11 @@ dependencies = [ "rkyv", "tts-frontend", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2496,12 +2551,12 @@ dependencies = [ "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2516,7 +2571,7 @@ version = "0.1.0" dependencies = [ "log", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-ticktimer", ] @@ -2533,12 +2588,12 @@ dependencies = [ "rkyv", "spinor", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2614,7 +2669,7 @@ dependencies = [ "num-derive", "num-traits", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", @@ -2638,12 +2693,12 @@ dependencies = [ "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-semver", ] @@ -2697,12 +2752,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" version = "2.5.0" @@ -2801,11 +2850,11 @@ dependencies = [ "trng", "tts-frontend", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2831,17 +2880,16 @@ dependencies = [ "pddb", "percent-encoding", "rkyv", - "rustls", "serde", "tls", "trng", "ureq", "url", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2867,11 +2915,11 @@ dependencies = [ "serde", "trng", "ureq", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2907,12 +2955,12 @@ dependencies = [ "smoltcp", "trng", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-semver", ] @@ -3052,7 +3100,16 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" dependencies = [ - "num_enum_derive", + "num_enum_derive 0.5.7", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive 0.6.1", ] [[package]] @@ -3066,6 +3123,17 @@ dependencies = [ "syn 1.0.103", ] +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.23", +] + [[package]] name = "oid-registry" version = "0.6.1" @@ -3093,6 +3161,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "option-block" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f2c5d345596a14d7c8b032a68f437955f0059f2eb9a5972371c84f7eef3227" + [[package]] name = "orbclient" version = "0.3.32" @@ -3298,12 +3372,12 @@ dependencies = [ "trng", "tts-frontend", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", "zeroize_derive", ] @@ -3321,9 +3395,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "perflib" @@ -3331,7 +3405,7 @@ version = "0.1.0" dependencies = [ "log", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3381,7 +3455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" dependencies = [ "arrayvec", - "num_enum", + "num_enum 0.5.7", "paste", ] @@ -3884,11 +3958,11 @@ dependencies = [ "num-derive", "num-traits", "trng", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3926,9 +4000,9 @@ dependencies = [ "spin 0.5.2", "untrusted", "winapi", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-names", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4010,12 +4084,12 @@ dependencies = [ "tts-frontend", "usb-device-xous", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-semver", "zeroize", ] @@ -4333,12 +4407,12 @@ dependencies = [ "rkyv", "trng", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4427,12 +4501,48 @@ dependencies = [ "usb-device-xous", "utralib", "x25519-dalek", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sigchat" +version = "0.1.0" +dependencies = [ + "chat", + "codec", + "com", + "com_rs 0.1.0 (git+https://github.com/betrusted-io/com_rs?rev=891bdd3ca8e41f81510d112483e178aea3e3a921)", + "content-plugin-api", + "gam", + "graphics-server", + "ime-plugin-api", + "ime-plugin-shell", + "llio", + "locales", + "log", + "modals", + "net", + "num-derive", + "num-traits", + "pddb", + "percent-encoding", + "rkyv", + "rustls", + "serde", + "tls", + "trng", + "ureq", + "url", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-api-log", + "xous-api-names", + "xous-api-ticktimer", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4466,13 +4576,13 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smoltcp" version = "0.10.0" -source = "git+https://github.com/smoltcp-rs/smoltcp.git?branch=main#edfdb23243a973d458397a2b4e76f527bacb1fd6" +source = "git+https://github.com/smoltcp-rs/smoltcp.git?branch=main#9624adf20cbeaf627e907a87b4fdf74e8f3154fb" dependencies = [ "bitflags 1.3.2", "byteorder", "cfg-if", "defmt", - "heapless", + "heapless 0.8.0", "libc", "log", "managed", @@ -4487,6 +4597,15 @@ dependencies = [ "no-std-net", ] +[[package]] +name = "spawn" +version = "0.1.0" +dependencies = [ + "log", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-api-log", +] + [[package]] name = "spin" version = "0.5.2" @@ -4516,12 +4635,12 @@ dependencies = [ "rkyv", "trng", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4577,12 +4696,12 @@ dependencies = [ "usb-device-xous", "userprefs", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-semver", ] @@ -4764,7 +4883,7 @@ name = "ticktimer-test-client" version = "0.1.0" dependencies = [ "log", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", ] @@ -4859,11 +4978,11 @@ dependencies = [ "ureq", "webpki-roots 0.24.0", "x509-parser", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4976,10 +5095,10 @@ dependencies = [ "num-derive", "num-traits", "usb-device-xous", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4994,12 +5113,12 @@ dependencies = [ "rand_core 0.5.1", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -5012,11 +5131,11 @@ dependencies = [ "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-tts-backend", ] @@ -5059,9 +5178,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" @@ -5071,9 +5190,9 @@ checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] @@ -5132,13 +5251,12 @@ dependencies = [ [[package]] name = "url" -version = "2.2.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -5163,26 +5281,26 @@ dependencies = [ "modals", "num-derive", "num-traits", - "num_enum", + "num_enum 0.5.7", "packed_struct", "rand 0.8.5", "rand_chacha 0.3.1", "rkyv", "trng", "usb-device", - "usbd-human-interface-device 0.2.1", "usbd-serial", "usbd_mass_storage 0.1.0", "usbd_scsi 0.1.1", "utralib", "vcell", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-semver", + "xous-usb-hid", ] [[package]] @@ -5198,48 +5316,14 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "usb-device", - "usbd-human-interface-device 0.1.1", "utralib", "vcell", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", -] - -[[package]] -name = "usbd-human-interface-device" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262ce4294612a8fc830daeaded8b111e241dff8520965979d1754eb87fb33951" -dependencies = [ - "delegate 0.6.2", - "embedded-time", - "frunk", - "hash32", - "hash32-derive", - "heapless", - "log", - "num_enum", - "packed_struct", - "usb-device", -] - -[[package]] -name = "usbd-human-interface-device" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f01ee9d05b01603f4d6ee43e7d0ebe140f16567d6ff99fde0dc1ea6e7564e65" -dependencies = [ - "delegate 0.7.0", - "embedded-time", - "frunk", - "heapless", - "log", - "num_enum", - "packed_struct", - "usb-device", + "xous-usb-hid", ] [[package]] @@ -5264,7 +5348,7 @@ dependencies = [ "typenum", "usb-device", "usbd_mass_storage 0.1.0", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-ticktimer", ] @@ -5338,7 +5422,7 @@ dependencies = [ "usb-device", "usbd_bulk_only_transport 0.1.0", "usbd_mass_storage 0.1.0", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-ticktimer", ] @@ -5414,15 +5498,15 @@ dependencies = [ "trng", "tts-frontend", "usb-device-xous", - "usbd-human-interface-device 0.2.1", "userprefs", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-usb-hid", ] [[package]] @@ -5955,7 +6039,7 @@ checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" [[package]] name = "xous" -version = "0.9.53" +version = "0.9.55" dependencies = [ "compiler_builtins", "lazy_static", @@ -5964,94 +6048,94 @@ dependencies = [ [[package]] name = "xous" -version = "0.9.53" +version = "0.9.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91c57a2ea348eefbeb08c42daf5c29439b2122516906a0e29fa1522162daf62" +checksum = "015f1d2e88db95364c4eaf75e853b03ffadabb64fab7e9d7ae170b2c7c4bfc85" dependencies = [ "lazy_static", ] [[package]] name = "xous-api-log" -version = "0.1.49" +version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a48cf4707962cd5e0cf5a471918da348dd586c50018d3d2cba45e1471a720f" +checksum = "8589f62095d64a4ac0c00b60c86446b5d48d6185fde91119c7244e2604f79b65" dependencies = [ "log", "num-derive", "num-traits", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "xous-api-names" -version = "0.9.51" +version = "0.9.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63af0102f160d178ff6be9f370f3c80c935c780c7670588eb9bf4182eac5f83" +checksum = "5facaaf64653d6a7a10420a4513b4c45172c713c0fdd25a7694cfee66691eece" dependencies = [ "log", "num-derive", "num-traits", "rkyv", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "xous-api-susres" -version = "0.9.49" +version = "0.9.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb2042f9a73fcfc1b4b448b034aa939ad427a34481ef87a0f3887da674b1647" +checksum = "8afdf6aed211138f0bde23e0300c1f918a8fbed568361b67d9f6bd54a840640f" dependencies = [ "log", "num-derive", "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "xous-api-ticktimer" -version = "0.9.49" +version = "0.9.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a523b2d226effa0ee2218eb2cb47f1bd51bf6167bf9bd2bccad3a25b86d11b" +checksum = "57ecf7abc203eaf9c47663754986854f34c135a72ee9ad859329a2bc55143828" dependencies = [ "log", "num-derive", "num-traits", "rkyv", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-semver", ] [[package]] name = "xous-ipc" -version = "0.9.53" +version = "0.9.55" dependencies = [ "bitflags 1.3.2", "rkyv", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "xous-ipc" -version = "0.9.53" +version = "0.9.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3b566abdb43129f7921cf05f734706cd8fbe18a801c2668910979a0aefe687" +checksum = "4c86d59376c078f289ac052f4697807cfaccc3a4d61a52de09d3a7bf872abbb6" dependencies = [ "bitflags 1.3.2", "rkyv", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -6071,7 +6155,7 @@ dependencies = [ "rand_chacha 0.3.1", "stats_alloc", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-riscv", ] @@ -6085,9 +6169,9 @@ dependencies = [ "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -6099,10 +6183,10 @@ dependencies = [ "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -6114,7 +6198,7 @@ dependencies = [ "pio", "pio-proc", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -6126,7 +6210,7 @@ dependencies = [ "pio", "pio-proc", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-pio", ] @@ -6155,11 +6239,11 @@ dependencies = [ "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -6172,12 +6256,12 @@ dependencies = [ "num-traits", "rkyv", "utralib", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-log", "xous-api-names", "xous-api-susres", "xous-api-ticktimer", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-semver", ] @@ -6191,9 +6275,23 @@ dependencies = [ "num-derive", "num-traits", "rkyv", - "xous 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", "xous-api-names", - "xous-ipc 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-ipc 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "xous-usb-hid" +version = "0.4.3" +source = "git+https://github.com/betrusted-io/xous-usb-hid.git#eba35e886d08e504cf10026221aec0fbfc63e201" +dependencies = [ + "frunk", + "fugit", + "heapless 0.7.16", + "num_enum 0.6.1", + "option-block", + "packed_struct", + "usb-device", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1180960e9..6bc1c7802 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,8 @@ members = [ "services/dns", "services/modals", "services/early_settings", + "apps/app-loader", + "apps/app-loader/spawn", "apps/ball", "apps/hello", "apps/mtxchat", @@ -81,6 +83,7 @@ members = [ "apps/vault", "apps/vault/tools/vaultbackup-rs", "apps/transientdisk", + "apps/chat-test", "services/libstd-test", "services/ffi-test", "services/tts", @@ -149,8 +152,6 @@ git="https://github.com/betrusted-io/usb-device.git" branch="main" # local dev shortcuts #path = "../usb-device" -#[patch.crates-io.usbd-human-interface-device] -#path = "../usbd-human-interface-device" [patch.crates-io.getrandom] path = "imports/getrandom" diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt deleted file mode 100644 index d64569567..000000000 --- a/LICENSES/Apache-2.0.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 120000 index 000000000..ea5b60640 --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/RELEASE-v0.9.md b/RELEASE-v0.9.md index f80095e0b..2d99d02a8 100644 --- a/RELEASE-v0.9.md +++ b/RELEASE-v0.9.md @@ -411,6 +411,14 @@ perform the Xous firmware upgrade. This requires running manual update commands, - Network stack is updated to work with the `main` branch of `smoltcp`. This is not intended as a release configuration -- this note is here so that when a release is snapshotted out, we remember to pin to a particular commit (or ideally an official release, if one happens in time). - This fixes a number of long-standing issues, including #210 and #407. - Fairly major overhaul to the network stack. We now use mspc primitives to implement the wait/poll loop, which should make the net stack much more efficient and robust. +- mtxchat project kicked off by @nohj - see https://github.com/orgs/betrusted-io/projects/3/views/1 for project tracker +- mutex & condvar refactor in ticktimer thanks to @xobs - improves performance and stability +- App loader example code added thanks to @vihaanjim +- Add "busy spinner" primitive to text boxes to the GAM. This allows UIs to show that something is happening without having to explicitly implement that. +- sigchat skeleton added thanks to @nhoj +- usbd-human-interface-device bumped to latest in preparation for a fork +- Fix #446 - add support for numeric-only passwords in `vault` +- Fork usbd-human-interface-device to xous-usb-hid ## Roadmap - Lots of testing and bug fixes diff --git a/api/xous-api-log/Cargo.toml b/api/xous-api-log/Cargo.toml index 638ffc2f0..2152bae62 100644 --- a/api/xous-api-log/Cargo.toml +++ b/api/xous-api-log/Cargo.toml @@ -3,15 +3,15 @@ authors = ["Sean Cross "] description = "Log server API" edition = "2018" name = "xous-api-log" -version = "0.1.49" +version = "0.1.51" license = "MIT OR Apache-2.0" repository = "https://github.com/betrusted-io/xous-core/" homepage = "https://betrusted.io/xous-book/" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -xous-ipc = "0.9.53" +xous = "0.9.55" +xous-ipc = "0.9.55" log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} @@ -19,4 +19,5 @@ num-traits = {version = "0.2.14", default-features = false} [features] debugprint = [] # adding this allocates the UART for debugging the logger logging = [] # adding this allocates the hardware UART for console interactions +nostd = [] # adding this causes the program to be built without std default = ["logging"] diff --git a/api/xous-api-log/src/lib.rs b/api/xous-api-log/src/lib.rs index 7748c54b0..282c5d0f6 100644 --- a/api/xous-api-log/src/lib.rs +++ b/api/xous-api-log/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(any(target_os = "none", feature = "nostd"), no_std)] use core::fmt::Write; use core::sync::atomic::{AtomicU32, Ordering}; use num_traits::ToPrimitive; @@ -96,8 +96,10 @@ pub fn init() -> Result<(), LogError> { } pub fn init_wait() -> Result<(), ()> { + let sid = xous::SID::from_bytes(b"xous-log-server ").unwrap(); + let cid = xous::connect(sid).or(Err(()))?; XOUS_LOGGER_CONNECTION.store( - xous::connect(xous::SID::from_bytes(b"xous-log-server ").unwrap()).or(Err(()))?, + cid, Ordering::Relaxed, ); log::set_logger(&XOUS_LOGGER).or(Err(()))?; diff --git a/api/xous-api-names/Cargo.toml b/api/xous-api-names/Cargo.toml index 047883b09..abd2f911d 100644 --- a/api/xous-api-names/Cargo.toml +++ b/api/xous-api-names/Cargo.toml @@ -3,16 +3,16 @@ authors = ["bunnie "] description = "Xous microkernel OS inter-process name resolution server" edition = "2018" name = "xous-api-names" -version = "0.9.51" +version = "0.9.53" license = "MIT OR Apache-2.0" repository = "https://github.com/betrusted-io/xous-core/" homepage = "https://betrusted.io/" # Dependency versions enforced by Cargo.lock. [dependencies] -log-server = {package = "xous-api-log", version = "0.1.49"} -xous = "0.9.53" -xous-ipc = "0.9.53" +log-server = {package = "xous-api-log", version = "0.1.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} log = "0.4.14" diff --git a/api/xous-api-susres/Cargo.toml b/api/xous-api-susres/Cargo.toml index 34966a4f7..ace00ee48 100644 --- a/api/xous-api-susres/Cargo.toml +++ b/api/xous-api-susres/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xous-api-susres" -version = "0.9.49" +version = "0.9.51" authors = ["bunnie "] edition = "2018" description = "Manager of suspend/resume operations" @@ -10,10 +10,10 @@ homepage = "https://betrusted.io/xous-book/" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/api/xous-api-ticktimer/Cargo.toml b/api/xous-api-ticktimer/Cargo.toml index c5673de2c..9077b6c2f 100644 --- a/api/xous-api-ticktimer/Cargo.toml +++ b/api/xous-api-ticktimer/Cargo.toml @@ -3,18 +3,18 @@ authors = ["bunnie ", "Sean Cross "] description = "Provide high-resolution, non-rollover system time" edition = "2018" name = "xous-api-ticktimer" -version = "0.9.49" +version = "0.9.51" license = "MIT OR Apache-2.0" repository = "https://github.com/betrusted-io/xous-core/" homepage = "https://betrusted.io/xous-book/" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = {package = "xous-api-log", version = "0.1.49"} -susres = {package = "xous-api-susres", version = "0.9.49"} -xous-names = {package = "xous-api-names", version = "0.9.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = {package = "xous-api-log", version = "0.1.51"} +susres = {package = "xous-api-susres", version = "0.9.51"} +xous-names = {package = "xous-api-names", version = "0.9.53"} log = "0.4.14" rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} num-derive = {version = "0.3.3", default-features = false} diff --git a/apps/app-loader/Cargo.toml b/apps/app-loader/Cargo.toml new file mode 100644 index 000000000..5e7ea672e --- /dev/null +++ b/apps/app-loader/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "app-loader" +version = "0.1.0" +authors = ["Vihaan Jim "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +log = "0.4.14" +xous-names = { package = "xous-api-names", version = "0.9.53" } +gam = { path = "../../services/gam" } +graphics-server = { path = "../../services/graphics-server" } +modals = { path = "../../services/modals" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +locales = { path = "../../locales" } + +num-derive = {version = "0.3.3", default-features = false} +num-traits = {version = "0.2.14", default-features = false} + +rkyv = {version = "0.4.3", features = ["const_generics"], default-features = false} + +ureq = { version = "2.5.0", features = ["json"] } +url = "2.4" \ No newline at end of file diff --git a/apps/app-loader/README.md b/apps/app-loader/README.md new file mode 100644 index 000000000..0bb974ab4 --- /dev/null +++ b/apps/app-loader/README.md @@ -0,0 +1,47 @@ +# An App Loader for Xous + +This app allows you to load apps onto your device at runtime and is intended to be used by developers who would like to quickly test their apps on physical hardware. +At the moment the apps are loaded onto the device using a primitive HTTP server; however, in the future, other (faster) methods may be implemented. +Depending on how good the device's WiFi connection is, loading the app can take anywhere from 20 seconds for a small app to a couple of minutes for a large app. Additionally, if you are loading a particularly large app, your device's WiFi connection may cut out while you are trying to load it, leading to your device not being able to download the app (this is another reason why different methods of app loading may need to be implemented). +Note that at the moment you can only load apps once each time the device runs since process destruction has not been implemented in Xous yet. + +## Usage + +First, add the app loader to your device's image as you would with any other app: +``` +$ cargo xtask app-image-xip --app app-loader --feature unsafe-app-loading +$ sudo python3 tools/usb_update.py -k target/riscv32imac-unknown-xous-elf/release/xous.img -l target/riscv32imac-unknown-xous-elf/release/loader.bin +``` +Then, compile the apps you would like on your device and start the app server. For example, if you would like to serve the apps `hello`, `ball`, and `vault` on port 8000, run: +``` +$ cargo xtask compile-apps --app hello --app ball --app vault +$ python3 tools/app_server.py hello ball vault -p 8000 +``` +Now, after selecting `App Loader` from the app menu on the device, you should see a menu that looks like: +``` +/------------\ +| Set Server | +| Close | +\------------/ +``` +From here, you can select `Set Server` and then input the address of your server (e.g., `http://` followed by the ip address of your server, a `:`, and the port you are running it on). If you do not prefix the address with `http://`, the app will present an error and make you re-enter it. +Now, the screen should look like: +``` +/-----------------\ +| Add App | +| Reload App List | +| Set Server | +| Close | +\-----------------/ +``` +If you select `Add App`, this should bring up a submenu with a list of the apps served on your app server. Selecting one of these apps will load it onto the device. The app you load will now show up in the app loader menu. For example, if you loaded `Hello World`, the menu should look like: +``` +/-----------------\ +| Hello World | +| Add App | +| Reload App List | +| Set Server | +| Close | +\-----------------/ +``` +Clicking on `Hello World` (or the name of some other app you loaded) should now open the app. diff --git a/apps/app-loader/locales/i18n.json b/apps/app-loader/locales/i18n.json new file mode 100644 index 000000000..57801ed9d --- /dev/null +++ b/apps/app-loader/locales/i18n.json @@ -0,0 +1,50 @@ +{ + "apploader.close": { + "en": "Close", + "en-tts": "Close" + }, + "apploader.menu.setserver": { + "en": "Set Server", + "en-tts": "Set Server" + }, + "apploader.setserver.error": { + "en": "Invalid URL: ", + "en-tts": "Invalid URL: " + }, + "apploader.menu.reloadapplist": { + "en": "Reload App List", + "en-tts": "Reload App List" + }, + "apploader.reloadapplist.loading": { + "en": "Loading App List...", + "en-tts": "Loading App List" + }, + "apploader.reloadapplist.connection_error": { + "en": "Could not connect to server: ", + "en-tts": "Could not connect to server: " + }, + "apploader.reloadapplist.json_error": { + "en": "Could not convert to JSON: ", + "en-tts": "Could not convert to JSON: " + }, + "apploader.menu.addapp": { + "en": "Add App", + "en-tts": "Add App" + }, + "apploader.addapp.loading": { + "en": "Loading app...", + "en-tts": "Loading app" + }, + "apploader.addapp.error": { + "en": "Binary could not be loaded :(", + "en-tts": "Binary could not be loaded sad face" + }, + "apploader.addapp.content_length_error": { + "en": "No Content-Length header in server response", + "en-tts": "No Content-Length header in server response" + }, + "apploader.addapp.server_error": { + "en": "Could not connect to server: ", + "en-tts": "Could not connect to server: " + } +} diff --git a/apps/app-loader/spawn/Cargo.toml b/apps/app-loader/spawn/Cargo.toml new file mode 100644 index 000000000..cf0fbd1ac --- /dev/null +++ b/apps/app-loader/spawn/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "spawn" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51", features = ["nostd"] } +log = "0.4.17" diff --git a/apps/app-loader/spawn/README.md b/apps/app-loader/spawn/README.md new file mode 100644 index 000000000..807eef81d --- /dev/null +++ b/apps/app-loader/spawn/README.md @@ -0,0 +1,24 @@ +# Spawn a process & Read an ELF File + +This crate is based on the one in services/test_spawn. The main +difference is that this one has the ability to read, load, and +execute an ELF file through the opcode `LoadElf`. + +## API + +The following messages are supported: + +| Mnemonic | Opcode | Type | Description | +|--------------|--------|------|----------------------------------------------------------------------------------------------------------| +| LoadElf | 1 | M | Reads and loads an ELF file sent as a MemoryMessage. `Offset` is used to determine where the file starts | +| PingResponse | 2 | S | Returns the scalar sent except that arg1 += 1 | + +## A Note on Building + +In case you need to recompile this program for use in `app-loader`, +use the following commands: +``` +$ cargo build --package spawn --target riscv32imac-unknown-xous-elf --release +$ cargo run --package tools --bin copy-object target/riscv32imac-unknown-xous-elf/release/spawn +$ cp target/riscv32imac-unknown-xous-elf/release/spawn.bin apps/app-loader/src +``` diff --git a/apps/app-loader/spawn/build.rs b/apps/app-loader/spawn/build.rs new file mode 100644 index 000000000..2fbc2e034 --- /dev/null +++ b/apps/app-loader/spawn/build.rs @@ -0,0 +1,33 @@ +// NOTE: Adapted from cortex-m/build.rs +use std::env; +use std::fs; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let target = env::var("TARGET").unwrap(); + // let name = env::var("CARGO_PKG_NAME").unwrap(); + + if target.starts_with("riscv") { + // fs::copy( + // format!("bin/{}.a", target), + // out_dir.join(format!("lib{}.a", name)), + // ).unwrap(); + + // println!("cargo:rustc-link-lib=static={}", name); + // println!("cargo:rustc-link-search={}", out_dir.display()); + // println!("cargo:rerun-if-changed=bin/{}.a", target); + println!("cargo:rustc-link-arg=-Tlink.x"); + } + + // Put the linker script somewhere the linker can find it + fs::File::create(out_dir.join("link.x")) + .unwrap() + .write_all(include_bytes!("link.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out_dir.display()); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=link.x"); +} diff --git a/apps/app-loader/spawn/link.x b/apps/app-loader/spawn/link.x new file mode 100644 index 000000000..c24dd7e3d --- /dev/null +++ b/apps/app-loader/spawn/link.x @@ -0,0 +1,190 @@ +MEMORY +{ + RAM : ORIGIN = 0x40000000, LENGTH = 16M + FLASH : ORIGIN = 0x20501000, LENGTH = 188k + FONTS : ORIGIN = 0x20530000, LENGTH = 4352k + MEMLCD: ORIGIN = 0xB0000000, LENGTH = 32k +} +/* +Fonts go from 0x2053_0000 to 0x2097_0000 +*/ + +REGION_ALIAS("REGION_TEXT", FLASH); +REGION_ALIAS("REGION_RODATA", FLASH); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_STACK", RAM); +REGION_ALIAS("REGION_HEAP", RAM); + +_lcdfb = ORIGIN(MEMLCD); +/* Size of the main kernel stack */ +_stack_size = 16K; +_eheap = ORIGIN(RAM) + LENGTH(RAM); + + +PROVIDE(_stext = ORIGIN(REGION_TEXT)); +PROVIDE(_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(trap_handler = default_trap_handler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +ENTRY(init) + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = _stext; + } > REGION_TEXT + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.text.init)); + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + KEEP(*(.trap)); + KEEP(*(.trap.rust)); + + *(.text .text.*); + } > REGION_TEXT + + .rodata : ALIGN(4) + { + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + _etext = .; + } > REGION_RODATA + + .data : ALIGN(4) + { + _sidata = LOADADDR(.data); + _sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(4); + _edata = .; + } > REGION_DATA AT > REGION_RODATA + + .fonts ALIGN(65536) : SUBALIGN(65536) + { + KEEP(*(.fontdata)); + } > FONTS + + .bss (NOLOAD) : + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(4); + _ebss = .; + } > REGION_BSS + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _sstack = .; + . += _stack_size; + . = ALIGN(4096); + _estack = .; + } > REGION_STACK + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + . = ALIGN(4); + _sheap = .; + /* _eheap is defined elsewhere and is the remainder of RAM */ + } > REGION_HEAP + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + /* Discard .eh_frame, we are not doing unwind on panic so it is not needed */ + /DISCARD/ : + { + *(.eh_frame); + *(.eh_frame_hdr); + } +} + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_RODATA) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_RODATA must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_DATA) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_DATA must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_HEAP) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_HEAP must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_STACK) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_STACK must be 4-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 4 == 0 && _edata % 4 == 0, " +BUG(riscv-rt): .data is not 4-byte aligned"); + +ASSERT(_sidata % 4 == 0, " +BUG(riscv-rt): the LMA of .data is not 4-byte aligned"); + +ASSERT(_sbss % 4 == 0 && _ebss % 4 == 0, " +BUG(riscv-rt): .bss is not 4-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT), " +ERROR(riscv-rt): The .text section must be placed inside the REGION_TEXT region. +Set _stext to an address smaller than 'ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/apps/app-loader/spawn/src/main.rs b/apps/app-loader/spawn/src/main.rs new file mode 100644 index 000000000..699725170 --- /dev/null +++ b/apps/app-loader/spawn/src/main.rs @@ -0,0 +1,190 @@ +#![no_std] +#![no_main] + +enum StartupCommand { + Unhandled = 0, + LoadElf = 1, + PingResponse = 2, +} + +impl From for StartupCommand { + fn from(src: xous::MessageId) -> StartupCommand { + match src { + 1 => StartupCommand::LoadElf, + 2 => StartupCommand::PingResponse, + _ => StartupCommand::Unhandled, + } + } +} + +#[panic_handler] +fn handle_panic(arg: &core::panic::PanicInfo) -> ! { + log::info!("{arg}"); + xous::terminate_process(1); +} + +#[no_mangle] +pub extern "C" fn init(server1: u32, server2: u32, server3: u32, server4: u32) -> ! { + let server = xous::SID::from_u32(server1, server2, server3, server4); + + // recreate the extra sections that were cut out of the stub + { + let mut memory = xous::map_memory( + None, + core::num::NonZeroUsize::new(0x40000000), + 0x1000, + xous::MemoryFlags::R | xous::MemoryFlags::W, + ) + .unwrap(); + let connection = core::sync::atomic::AtomicU32::new(0); + let slice = unsafe { + core::slice::from_raw_parts( + &connection as *const _ as *const u8, + core::mem::size_of::(), + ) + }; + // safety: memory is freshly allocated by map_memory, should be aligned and init'd to 0 + unsafe { + for (dest, src) in memory.as_slice_mut::().iter_mut().skip(8).zip(slice) { + *dest = *src; + } + } + // everything should drop now + } + + log_server::init_wait().unwrap(); + log::set_max_level(log::LevelFilter::Info); + log::info!("my PID is {}", xous::process::id()); + loop { + if let Ok(xous::Result::MessageEnvelope(mut envelope)) = + xous::rsyscall(xous::SysCall::ReceiveMessage(server)) + { + match envelope.id().into() { + StartupCommand::LoadElf => { + let entry_point = read_elf(envelope.body.memory_message_mut()); + drop(envelope); // we have to get rid of all messages to destroy the server + // destroy the server + xous::destroy_server(server).expect("Couldn't destroy spawn server"); + jump(entry_point); + } + StartupCommand::PingResponse => ping_response(envelope), + _ => panic!("Unsupported"), + } + } + } +} + +fn ping_response(envelope: xous::MessageEnvelope) { + if let Some(msg) = envelope.body.scalar_message() { + if envelope.body.is_blocking() { + xous::syscall::return_scalar(envelope.sender, msg.arg1 + 1).unwrap(); + } + } +} + +fn read_elf(memory: Option<&mut xous::MemoryMessage>) -> usize { + let memory = match memory { + Some(s) => s, + None => panic!(), + }; + + // get the elf binary from the message + // safety: buf should be aligned and correctly sized inside the MemoryMessage + let mut bin = unsafe {memory.buf.as_slice::()}; + + // go to the beginning of the ELF file using the provided offset + bin = &bin[memory.offset.and_then(|n| Some(n.get())).unwrap_or(0)..]; + + let max = bin.len(); + + // make sure that this is a 32 bit + assert!(bin[4] == 0x01); + + // a helper function to get a region of the file as a usize + let to_usize = |start, size| { + if size == 1 { + return bin[start] as usize; + } + if size == 2 { + // assumes little endianness + return u16::from_le_bytes(bin[start..start + size].try_into().unwrap()) as usize; + } + if size == 4 { + return u32::from_le_bytes(bin[start..start + size].try_into().unwrap()) as usize; + } + panic!("Tried to get usize of invalid size!"); + }; + + // some basic stuff to know + let entry_point = to_usize(0x18, 4); + let ph_start = to_usize(0x1c, 4); + let ph_size = to_usize(0x2A, 2); + let ph_count = to_usize(0x2C, 2); + + // add the segments we should load + for i in 0..ph_count { + let start = ph_start + i * ph_size; + // only load PT_LOAD segments + if to_usize(start, 4) == 0x00000001 { + let src_addr = to_usize(start + 0x04, 4); + let vaddr = to_usize(start + 0x08, 4); + let padding = if vaddr & 0xFFF == 0 { 0 } else { vaddr & 0xFFF }; + let file_size = to_usize(start + 0x10, 4); + let mem_size = to_usize(start + 0x14, 4); + let mem_size = mem_size + padding; + let mem_size = mem_size + + if mem_size & 0xFFF == 0 { + 0 + } else { + 0x1000 - (mem_size & 0xFFF) + }; + + assert_eq!(0, mem_size & 0xFFF); + assert_eq!(0, (vaddr - padding) & 0xFFF); + + log::info!( + "Loading offset {} to virtual address {} with memory size {}", + src_addr, + vaddr, + mem_size + ); + let mut target_memory = xous::map_memory( + None, + core::num::NonZeroUsize::new(vaddr - padding), + mem_size, + xous::MemoryFlags::R | xous::MemoryFlags::W | xous::MemoryFlags::X, + ) + .unwrap(); + + // TODO: rationalize why this is safe + unsafe { + for (dest, src) in target_memory + .as_slice_mut() + .iter_mut() + .skip(padding) + .zip(bin[src_addr..core::cmp::min(max, src_addr + file_size)].iter()) + { + *dest = *src; + } + for dest in target_memory + .as_slice_mut() + .iter_mut() + .skip(padding + file_size) + .take(mem_size - file_size - padding) + { + *dest = 0; + } + } + } + } + + memory.offset = None; + log::info!("Finished writing"); + return entry_point; +} + +fn jump(entry_point: usize) -> ! { + log::info!("Jumping to {}", entry_point); + let entry_fn = unsafe { core::mem::transmute::<_, fn() -> !>(entry_point as *const u8) }; + entry_fn(); +} diff --git a/apps/app-loader/src/api.rs b/apps/app-loader/src/api.rs new file mode 100644 index 000000000..9a8a39101 --- /dev/null +++ b/apps/app-loader/src/api.rs @@ -0,0 +1,448 @@ +use crate::SERVER_NAME_APP_LOADER; +use gam::{ + menu_matic, Gam, MenuItem, MenuMatic, TextEntryPayload, UxRegistration, APP_MENU_0_APP_LOADER, + APP_MENU_1_APP_LOADER, APP_NAME_APP_LOADER, +}; +use locales::t; +use modals::Modals; +use num_traits::ToPrimitive; +use std::io::Read; + +pub(crate) struct AppLoader { + gam: Gam, + modals: Modals, + auth: [u32; 4], + ticktimer: ticktimer_server::Ticktimer, + menu: MenuMatic, + load_menu: MenuMatic, + conn: xous::CID, + apps: Vec>, + possible_apps: Vec<(xous_ipc::String<64>, usize)>, + server: Option, + current_menu: String, +} + +impl AppLoader { + pub(crate) fn new(xns: &xous_names::XousNames, sid: &xous::SID) -> AppLoader { + // gam + let gam = Gam::new(xns).expect("can't connect to GAM"); + + // the gam token + let auth = gam + .register_ux(UxRegistration { + app_name: xous_ipc::String::from_str(APP_NAME_APP_LOADER), + ux_type: gam::UxType::Modal, + predictor: None, + listener: sid.to_array(), + redraw_id: Opcode::Redraw.to_u32().unwrap(), + gotinput_id: None, + audioframe_id: None, + focuschange_id: None, + rawkeys_id: None, + }) + .expect("Couldn't register") + .expect("Didn't get an auth token"); + + let modals = Modals::new(xns).expect("Couldn't get modals"); + + // a connection to the server + let conn = xns + .request_connection_blocking(SERVER_NAME_APP_LOADER) + .expect("Couldn't connect to server"); + + // the ticktimer + let ticktimer = ticktimer_server::Ticktimer::new().expect("Couldn't connect to Ticktimer"); + + // the menu + let set_server_item = MenuItem { + name: xous_ipc::String::from_str(t!("apploader.menu.setserver", locales::LANG)), + action_conn: Some(conn), + action_opcode: Opcode::SetServer.to_u32().unwrap(), + action_payload: gam::MenuPayload::Scalar([0, 0, 0, 0]), + close_on_select: true, + }; + let close_item = MenuItem { + name: xous_ipc::String::from_str(t!("apploader.close", locales::LANG)), + action_conn: None, + action_opcode: 0, + action_payload: gam::MenuPayload::Scalar([0, 0, 0, 0]), + close_on_select: true, + }; + let menu = menu_matic( + vec![set_server_item, close_item.clone()], + APP_MENU_0_APP_LOADER, + Some(xous::create_server().unwrap()), + ) + .expect("Couldn't create menu"); + let load_menu = menu_matic( + vec![close_item.clone()], + APP_MENU_1_APP_LOADER, + Some(xous::create_server().unwrap()), + ) + .expect("Couldn't create menu"); + + AppLoader { + gam, + modals, + auth, + conn, + ticktimer, + menu, + load_menu, + apps: Vec::new(), + possible_apps: Vec::new(), + server: None, + current_menu: APP_MENU_0_APP_LOADER.to_string(), + } + } + + pub(crate) fn add_app(&mut self, index: usize) { + let (name, menus) = self.possible_apps[index]; + + self.modals + .start_progress(t!("apploader.addapp.loading", locales::LANG), 0, 3, 0) + .expect("Couldn't set up progress bar"); + + ////////////////////////// + // The downloading part // + ////////////////////////// + let response = match ureq::get(&format!( + "{}/{}", + self.server + .as_ref() + .expect("AddApp was somehow called without a server!"), + name + )) + .call() + { + Ok(response) => response, + Err(e) => { + self.modals + .show_notification( + &format!( + "{}{}", + t!("apploader.addapp.server_error", locales::LANG), + e + ), + None, + ) + .expect("Couldn't show modal"); + return; + } + }; + + let len = match response.header("Content-Length") { + Some(len) => len + .parse::() + .expect("Couldn't parse Content-Length header"), + None => { + self.modals + .show_notification( + t!("apploader.addapp.content_length_error", locales::LANG), + None, + ) + .expect("Couldn't show modal"); + return; + } + }; + + let mut memory = xous::map_memory( + None, + None, + len + if len & 0xFFF == 0 { + 0 + } else { + 0x1000 - (len & 0xFFF) + }, + xous::MemoryFlags::R | xous::MemoryFlags::W, + ) + .expect("Couldn't map memory"); + response + .into_reader() + .read_exact(&mut unsafe { memory.as_slice_mut() }[..len]) + .expect("Couldn't read"); + + self.modals + .update_progress(1) + .expect("Couldn't update progress"); + ////////////////////// + // The loading part // + ////////////////////// + + // create the spawn process + let stub = include_bytes!("spawn.bin"); + let args = xous::ProcessArgs::new( + stub, + xous::MemoryAddress::new(0x2050_1000).unwrap(), + xous::MemoryAddress::new(0x2050_1000).unwrap(), + ); + let spawn = xous::create_process(args).expect("Couldn't create spawn process"); + log::info!("Spawn PID: {}, Spawn CID: {}", spawn.pid, spawn.cid); + + // perform a ping to make sure that spawn is running + let result = + xous::send_message(spawn.cid, xous::Message::new_blocking_scalar(2, 1, 2, 3, 4)) + .unwrap(); + assert_eq!(xous::Result::Scalar1(2), result); + + self.modals + .update_progress(2) + .expect("Couldn't update progress"); + + // load the app from the binary file + let res = xous::send_message( + spawn.cid, + xous::Message::new_lend_mut(1, memory, None, None), + ) + .expect("Couldn't send a message to spawn"); + // we are just going to do some very basic error handling: if the "offset" is None, we are good, otherwise there was a problem + // TODO: make this better. Perhaps Buffer::from_raw_parts? + match res { + xous::Result::MemoryReturned(None, _) => self + .modals + .update_progress(3) + .expect("Couldn't update progress"), + _ => { + self.modals + .finish_progress() + .expect("Couldn't close progressbar"); + self.modals + .show_notification(t!("apploader.addapp.error", locales::LANG), None) + .expect("Couldn't show modal"); + return; + } + } + + ////////////////////// + // back to graphics // + ////////////////////// + + // add its name to GAM + self.gam + .register_name(name.to_str(), self.auth) + .expect("Couldn't register name"); + for menu in 0..menus { + self.gam + .register_name(&format!("{} Submenu {}", name, menu), self.auth) + .expect("Couldn't register name"); + } + + // add it to the menu + self.menu.insert_item( + MenuItem { + name, + action_conn: Some(self.conn), + action_opcode: Opcode::DispatchApp.to_u32().unwrap(), + action_payload: gam::MenuPayload::Scalar([ + self.apps.len().try_into().unwrap(), + 0, + 0, + 0, + ]), + close_on_select: true, + }, + 0, + ); + self.apps.push(name); + + log::info!("Added app `{}'!", name); + + self.modals + .finish_progress() + .expect("Couldn't close progressbar"); + let _ = self.gam.switch_to_app(APP_NAME_APP_LOADER, self.auth); // try to switch back to the menu + } + + pub(crate) fn set_server(&mut self) { + let payload = self + .modals + .alert_builder("Server Address") + .field( + Some("e.g. http://ip:port".to_string()), + Some( + |payload: TextEntryPayload| match url::Url::parse(payload.as_str()) { + Ok(_) => None, + Err(e) => Some(xous_ipc::String::from_str(&format!( + "{}{}", + t!("apploader.setserver.error", locales::LANG), + e + ))), + }, + ), + ) + .build() + .ok(); + + if self.server.is_none() && payload.is_some() { + self.menu.insert_item( + MenuItem { + name: xous_ipc::String::from_str(t!( + "apploader.menu.reloadapplist", + locales::LANG + )), + action_conn: Some(self.conn), + action_opcode: Opcode::ReloadAppList.to_u32().unwrap(), + action_payload: gam::MenuPayload::Scalar([0, 0, 0, 0]), + close_on_select: true, + }, + 0, + ); + self.menu.insert_item( + MenuItem { + name: xous_ipc::String::from_str(t!("apploader.menu.addapp", locales::LANG)), + action_conn: Some(self.conn), + action_opcode: Opcode::AddAppMenu.to_u32().unwrap(), + action_payload: gam::MenuPayload::Scalar([0, 0, 0, 0]), + close_on_select: true, + }, + 0, + ); + } + + self.server = payload.and_then(|p| Some(p.first().as_str().to_string())); + self.reload_app_list(); + } + + pub(crate) fn open_load_menu(&mut self) { + self.current_menu = APP_MENU_1_APP_LOADER.to_string(); + let _ = self.gam.switch_to_app(APP_NAME_APP_LOADER, self.auth); + } + + pub(crate) fn reload_app_list(&mut self) { + // without a path, the server responds with a JSON list of strings representing the list of app names + self.modals + .start_progress( + t!("apploader.reloadapplist.loading", locales::LANG), + 0, + 3, + 0, + ) + .expect("Couldn't start progressbar"); + + let old = self.possible_apps.clone(); + + // this... disgusting error handling is so that if there is an error on the server side there isn't a panic + self.possible_apps = match match ureq::get( + &self + .server + .as_ref() + .expect("ReloadAppList was somehow called without a server!"), + ) + .call() + { + Ok(c) => { + self.modals + .update_progress(1) + .expect("Couldn't update progress"); + c + } + Err(e) => { + self.modals + .show_notification( + &format!( + "{}{}", + t!("apploader.reloadapplist.connection_error", locales::LANG), + e + ), + None, + ) + .expect("Couldn't show modal"); + return; + } + } + .into_json::>() + { + Ok(json) => { + self.modals + .update_progress(2) + .expect("Couldn't update progress"); + json + } + Err(e) => { + self.modals + .show_notification( + &format!( + "{}{}", + t!("apploader.reloadapplist.json_error", locales::LANG), + e + ), + None, + ) + .expect("Couldn't show modal"); + return; + } + } + .iter() + .map(|(name, menus)| (xous_ipc::String::<64>::from_str(&name), *menus)) + .collect(); + + for (old_name, _) in old { + self.load_menu.delete_item(old_name.as_str().unwrap()); + } + + for (i, (app, _)) in self.possible_apps.iter().enumerate() { + self.load_menu.insert_item( + MenuItem { + name: xous_ipc::String::from_str(app.to_str()), + action_conn: Some(self.conn), + action_opcode: Opcode::AddApp.to_u32().unwrap(), + action_payload: gam::MenuPayload::Scalar([i.try_into().unwrap(), 0, 0, 0]), + close_on_select: true, + }, + 0, + ); + } + self.modals + .finish_progress() + .expect("Couldn't close progress bar"); + + let _ = self.gam.switch_to_app(APP_NAME_APP_LOADER, self.auth); // try to switch back to the menu + } + + pub(crate) fn dispatch_app(&self, index: usize) { + if index < self.apps.len() { + let name: xous_ipc::String<64> = self.apps[index]; + log::info!("Switching to app `{}'", name); + self.gam + .switch_to_app(name.as_ref(), self.auth) + .expect(&format!("Could not dispatch app `{}'", name)); + } else { + panic!("Unrecognized app index"); + } + } + + pub(crate) fn redraw(&mut self) { + // Properly close the app menu + // for anyone who needs this I found this in Menu::key_event + self.gam.relinquish_focus().unwrap(); + xous::yield_slice(); + + self.ticktimer.sleep_ms(100).ok(); // yield for a moment to allow the previous menu to close + + // open the submenu if possible + let _ = self.gam.raise_menu(&self.current_menu); + // we only ever need the other menu for one thing + self.current_menu = APP_MENU_0_APP_LOADER.to_string(); + } +} + +#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] +pub(crate) enum Opcode { + /// set the server + SetServer, + + /// get a list of apps from the server + ReloadAppList, + + /// open the menu for adding apps + AddAppMenu, + + /// load an app and add it to the menu + AddApp, + + /// dispatch the app + DispatchApp, + + /// Redraw the UI + Redraw, +} diff --git a/apps/app-loader/src/main.rs b/apps/app-loader/src/main.rs new file mode 100644 index 000000000..f7fd08f2d --- /dev/null +++ b/apps/app-loader/src/main.rs @@ -0,0 +1,45 @@ +mod api; +use api::*; +use num_traits::FromPrimitive; + +const SERVER_NAME_APP_LOADER: &str = "_App Loader_"; + +fn main() -> ! { + log_server::init_wait().unwrap(); + log::set_max_level(log::LevelFilter::Info); + log::info!("my PID is {}", xous::process::id()); + + let xns = xous_names::XousNames::new().unwrap(); + let sid = xns + .register_name(SERVER_NAME_APP_LOADER, None) + .expect("Couldn't create server"); + + // start off by adding hello world + let mut app_loader = AppLoader::new(&xns, &sid); + + loop { + let msg = xous::receive_message(sid).unwrap(); + match FromPrimitive::from_usize(msg.body.id()).expect("Couldn't load message") { + Opcode::SetServer => { + app_loader.set_server(); + } + Opcode::ReloadAppList => { + app_loader.reload_app_list(); + } + Opcode::AddAppMenu => { + app_loader.open_load_menu(); + } + Opcode::AddApp => { + let index = msg.body.scalar_message().expect("Not a scalar").arg1; + app_loader.add_app(index); + } + Opcode::DispatchApp => { + let index = msg.body.scalar_message().expect("Not a scalar").arg1; + app_loader.dispatch_app(index); + } + Opcode::Redraw => { + app_loader.redraw(); + } + } + } +} diff --git a/apps/app-loader/src/spawn.bin b/apps/app-loader/src/spawn.bin new file mode 100644 index 0000000000000000000000000000000000000000..b479ecd844feea7185d62efb22a4b4c92c5361d7 GIT binary patch literal 21452 zcmdUX3s_XwweUW3<_yCK$~d6njp@J$;#3LDz#z0*206n+Flc~AV-nynC@JzDjqN1? zhBF9?k4(UrTiXc&mr&zPo{q3sN>mj4mG&betwFCSa?2#|%wzaHVIUB)7oexyY>ke5KT*)1(pbiFr3aysAqmf4 zMvo%xHJX{zv9OtD=69fYC0|~Ckf7U!cY1-2X{gKl6>Mp1fK@E zW8G7fzg(bOy+!3|M6bjK#HJ;Fnw)bw{iDQ>*M6cw`xOdS)I+pELXWA~yK=80(>0bK zvHQ)&G>T?y&FHp|s>27#C}tt+wi8XYaCFY2?cw3bw`PZrD%=Mp*F|15&^DT)fZ>_mKRbZyOh zEY18=hcsT$e)+!~K%9LPXsbYOKrP=A9R+T^I6HoO?Z=6yb3RHx2_>8@U1;WOqTljo z`M2)hA|%LeiS}eLNKUjU?P-Z9zaUY5-EIg4(9D6~AkhZ9%OlrpJjr9Ycdk)OZ?u!N zQPG91vb7n#(SeYcu|HvV@_ZFSn-RI%sFbvf$b|eA~-ts_j%y4K>yCF!WTKmLXos4(YLIBT-qtqewRDe+n7p^2zymz~9B& z8STCWdC3I{haXGvPo*;}%jL%TB<9wN2&HVue=u+$v45>A)krR75%b4DN>|lh6KG_v zoRG9t$&HEijJX5pWgXGJ4!sRLO}vCsdYrfD&u@2EjHqqAa1Vq+4HB~UO1 zCx4mKGm-6N1s^sW^vZd*%P&uPw(idmSXX7aiXbX9fy(DJiz}Yf`0syCqxO4V6C7Hm zS!OI#Nr~r`W)($M=!sXNRcRnur4t3$P-aCvl~X|rmh=)C;Et>T2?$$znGYH0F4g0Ac5fCTby3jfEt1fJVEgDqReM&ogI4I@wrh)hw5craei9I%7i z?O-J}FoFt9@cuC zF-6RO=Z5-@tStmHX?0L*R3~@YPacDyOSllY!82^s+#wXUtMzD zy1K~g3Krwg(J#;vQ=>^wn~8R(g-EVzciITQJtv`6^2NkyG&m!)qr429rET5rbX#wb zS6d$y+vuw7--iO(Az_7Z{Mqj+8Bhx|l0_<+6HW3Et{n|NGr0nv)t%Fx7iO`ot=kju zE%Q3s*LDxK?lEnZKYJ@BCv`N{w4rlD;KqiHvL|k>$w_h-{TM|ja`t3%7K0a%2zq!G zq3|NvfB1hG_(9@{wa3>x)8F6NbN>9Lb63t@JM-BnBco!pHV>K^q+;St%!TII_TA0V z^)z$ooiK8_326_JbTY&|%tIY9TWRLmdb*5eeo;@Wz+#!_>S^78iW-(Mh4pbHT}NnL zBz=g`_L20b5-a_ogfzWI=z2-hyM(UapVI%V12KwOffdP6Pe46EnywR8(;dQ}O0J8M zU6NVpcO_)pYm&4*`e#~DBc=cIWki|Fe6**wrn0ezIA@5ZA(bl5uJo zbNQX1YYn7p9<-K0>p*068N}AIPxqwl(T&{;+-ZAa z0QV<#?SFEjRcBOg6k(@mg30+~AmI0h*pguK{@tJX<2ki|YWp|U=$Qk6#R(Jw7V8=} zL?+Bf{?xiU(#2yWla?TBLIeWoP}mmJ>)S10Pn|x9&1q*lqs!*qsDt$m>CWmcHl#hr z%%{}TX79Jnk}Y0s{gSk{0gnYo2it~{791UJ8!4u>=R9fc`O3cC@}u2g!3!?YyuFm4 z=>6&X^mg{A#+~xFguOxeURNArb$cj1mZ7B!{N3BlJBSz$UD_pg{goO*zoiq_U5dEE zGGl}27|Z;z`JwizYE)DAO#LcbdbRrp%%wiBc3WRGQ}nSUs=?R6cs8JeC-CZo_W+}a zl2OzhpWKPdU8?a2#HgsbBWZfseF9pr{0YA9bodt745R|=$cCA z`B9{h9XkN{YX21xCc7;Te6+^lhB~V+-kJM2wiwoPtYPaP{Aqvc7@b`A0^LikH)#T8 z2*=)JpbPbyF3C>U)tK~uY(~s&mqf6u&^GXxZ3C6V;_BKqSjkx$*h=V5IWMUTo&M}M z(a*%5jZI8=J3*WHa-w3*ttWCejczhM+4-a=%*q_W6O@&VrejYMTkmJ#Z=p1h*dfqJZ1dui+)e+VPI=o#rG)oprf)sXu)o|5Q+I9 zln#35>hIg%(JS#96taL{Rng-xUOJ}Dv`XN%T%drWv|Svf)DPdH7m53*eRfK%2U~96Q85bS&S8PU1WhA7wv1#ZwXOJIR9ze3u`;o08GVDymy@{at}4__`n&@0hR0 zapB>?8#JTpK$Yc2rIw2%VMn9E<4oJN28`2%h^MAIQC!`+Ag;DHJ0Tusp6ldSH*r5C zu5Kw0THV+badlH4m`h9Qd3-$2wlH0v*2P7)+*+)@8ievDquL8UML;L!!Y-q{-A!1B zd@QdSuoTIT6LE#a()gbE&{n(b)`Wfg_#D;gIP~%hhv%f3nK>9+QeF3EZ zUmThIJ|n9Se%Hv+U|Z8i_TzZIhu^=>956DqQhy7)afcI^MIal2Fg zi{p~tXIypdca0mJfOY?5kr~FjGsF2qy1L+6B8f<- zNa&&_?y7c;f8NEGm)mzy@-82R!d}(vK(vQ3crf0mh1E6@iPlc*wf(Oqc{=n|CnD+b zi4EKiT7#OkpAhoQOy=V>CMUVk&h*LD@16exN(^T2O#;&`Ad$O^IXYGgG-x`7p+(iWU z>0EZIhzRbt2FxNz*(`}Qa26t$tU=Z#yAT=l0n(lgKBe*Kkr)P}hE@&7j07Pu>Sceb zJrjgR+4VMlUFjb2yu*1m%4rD#pURhJobi#$amKv7*Q9tU*bhINn#I;bUryLG*G>IMq# zvj!+Y2MW-E0(6iFI;aI541mOghc$B0L4VZ1s==5c1*;vwII*-<6zJiU2Pa+MtBq-M z?~!g>l}7p^Vig*7Dp=P{i^f)rGe|gR@c(-L=)#ey{Lyg2iwRnnZp1L$Pw%5o8Fw4C zL%Ko3Kroj>uv1hTmq$W4hu7_OEMJVr_CKBz=;8?NIRW#X)OZ0>Ri6bBRQ< z3362IHxu(Hp9+<`52BwV8mS*P5(Aa##q@6DDVUGU z0?KP=w04kW`yey-2hH%YNO!a5VF5r^jrWt{(Hf3`rU`T=r8`EVNfi zC1ZyaU)Ho*+fE>6mqJR!l5IeVz}aldljDq)?or(Fezza<(g?ixx~6yuy{M;Zk%)KE zRX^$2ztnz9HYXrv%d2R)gGk`si(ZwG(0jJK_8{(Sg&ZFln1HY}e0v}v<{%!hs!XjW z9UJYGm#~_kRxxUJW?(O;vat+~&G}UY9T973lTe#I6;rF*5E_O&ahpxY8f<+2RC6J+ zNOs=;T;SQnGiyiJpGqIuIQ-O5)?m)SGyVClwyOAKVU^is|7Rc6rBEWK$^+RIuS>8` z9QtL=<%%`CquI-^I4WpnwgNq68?ljYGNfAK_N^*zK6<z_H1Nt zrevj#q{eJGw!wen3mYX*++35DG?EmPd@R`yeAc;Gf5_FWwf)^z)#dRJ7n6AG;6Cz5 zEMa}EirCptt^W)^#G z=6EvY9c$o+D2w9*Y-7CLeV?&xW7!5hyOdyN(Qv{f;-cIwr}aNNNL=r*woLUHdzktQ zu}0GxOZd~8@+QfRj+`3(Z+}Ug?3kVw~(_B97!BrJG6c@fRy8TKU!fPl~B~!2L zu`V7!dMXn9NMg6Pmz73Hl}N$f@vi9S^?PCY*g^L0%2xghEsv0amF;v&s2-*)oM@+B zMOK!AEE)Qi`Phjn=!&cEtz(PACk7y!@M_S(o;EzvUc~~Y*FBI7N<>DQx%sLD?6Jmu zULsfo&L!AeWZ&8$d(&B8HV;k}Zyjud^Po8t?6@M)jhFSdrG(EOmBoOFSY~e^p?CWv zdZ5}`<%aA{(r@ifZxs9@<;J>|eN_>s{7|*sHDNelrJ%N%f?6MR^gx*pHc@LBQKC;h79(wY;QI-A?!7Px?Q{5Vxqw)?!55JgpmN5Ow_UY)( z7<=sa#G!KEigPh^-Htk4y~_2~#G=Y7(?RQ#dv@%xAOCXVko91>b8nyN_TE(uxc*D1 zKUhvfZ4Y?Yv0vG~In>4MBqcC2JecgGNCl%3v})Tb$e?&fE2Z}1w=j;%Pxsv3Q#8*| zZ@Gm%mVR3r@~hBW(vYrRPx+9>ine;@TMDvv3JDAI`pszOxJ%+QRFN3oD#l3XXgovl z8O{r-k_|T#vJyuUW7Zs718Z?LWRn+kinHmg5At=i)egU>)CnJPo3af@j-bUuqM;^?sZ8BB$JhY$( zpKsg8zna6GbCWT1wtJ|m199o`dC30tIH8iG+4ir#BAF``#kk{I1JsiJfyn;CW@O*~ zH3_@kIr=}}M9fvQgduPX6YlRcpj&eL$8VyAa(u4WZhjMmI=aBytZcQ{Z$Y2d`8kfs z+szAz>v2cjUfUZX|9qnNfJ*lD^LoQAgnhzmd)p(vKxKP;T+FpGbZ#x;rL|g=3mwhZ(uF>J_KKP;DRAdAQd1@Q+bX;_+nxLZXhh> zcP2x&M4==)h@?*cd}0%GT-S zmyc1ffadv7C*%v#f$`oTSp>cgNdLdgsgk zJF@jQrWm>W9d|md*(03H|8-(wGh!0t#(U~_%Z-Gi%hF=Avpx%xoL%+H>+LN4wTH7S zVB^Df)~rkF=?eHAoIr**d-PP6;Bzw??|0$0%IGo_d`9G*{^yyzMNXN>WZ(e?$qz#68>C6AX2_%g@~?4BtXxm=WN#vi^{|6SkN|M%ag zzjN<*_Wu~62kl5z=`^y-yq4G%g%T!vbT((R?xtpyd>x!Dxf|`XI`6d0gN(R7VqVut zyQIMqnWG|Qd*K$s?9RA|oV^k|+0aWpYMv2LQ`(UEI=5d0_y@m7P?if;`YKWn89|Nso4N0o9obM19Q0r>8o4tJr}pr(QF)Z<4+1Q4o(nh z3LP%Lf;8nVNXITUF+X=AmF=4e-0N5aoZXWFtujhwyEZWsu2m! z0`A-3|9OicTUo-|N)gsZ7Fmzdi2OGSSr0oAd6?$z;BjX(8%EEz}f!uu1kGadarfo zpAX)(I~f^;tSlF4&@8uV@7^EFc_sINRPL#8rm8$ExXhm2ll>_-^W80}6z$(@w>*TX zqz|uJfA|4G)!4_^phFjZuTEt`-A!#?iUmG3D53S2~L2h#mVQm?N z+hJ5vIy(X_mCi#Bt`ur1`tYn*P8+vRf-881POBZ+9`bG+`RAs9uwo1pJ_E2Xb|0lTn8 z9+35%%uaK1NaN(tnG%@VkSg17DsnJTvk<@AYg zle1~6+~;e)J^k=<)5Ruy+G2!hxHWJUfr;E z#BfSG8U-uVD!9|J1+{exmUoL^LEt994?<0!Hch9OQg3xkjqVgir~F=x})o^X(&W`1#@zyDf>|(wz8?e%JxoxW4d0W7F%g^MTCNDOSFQ*3r zOcxucQ|hxM+{Cs{FS)!V()8A}v7A97&Z0(4rQal#;Y)>@aqcO1-dbcjI0IMn(g2gY zSi;2A3$*DlCw_dvPBSE`w!q4Nz#89A{nl?)H#lC9KpRpE^1Q$TrO{! z{EFTomk&*TWjrgFk4%1*Z|C~UoVVoiJRv5E-WG%R0)(Z9itWIA0U@>?`ChTL=L7Zv zKGSR_e7V5y-xis?CuNcmuC1DR)&x>5|L?Z)=H=nj#zd=&y6_GqvkUK*y^Qi01=R(2 z3+y)A*M@p;h@~rSN|~;YmwY|Jt^RH{NjSEx$#Rwy%U3$MQrJiK*OU!9uE#O3{aSN}VVi zY$UK>qkz4rg0~Pj&ANl-Zs#n;-PC9ctmbW{`zL%f=M0m}m8OBo^5|83IUA14hbGIr ze=M};%10*4qkqJgQ{jSs!J?F=Qo*J&B1w83oDv!dez%J)?0bOej%58f?W983(NRgTCBY*m-~Wr_*%rxWjp2k zN|3>XT<$X|1NGAXP8nbcV7on9*;cKjw0#2AUvpZJQ3j3zx$_41Eob#y{cQTga+5M{ z|7z9j3N%mfM;C7Jb~cB~fZ8C}bwfWr6acu4UF}#C(|1;q^jz@h6W`~`P zgEPV|-Y?z6_G_25{FSp~x$H~sTP_>tp0iyYktWZ$SGjR{>n^{>k6Kwi47cteOM2hP ztK;|)$3Ettl+*a*yXY?f$P8dnS2Fl3v>GS zJUs+=b37PkGVkXFDSqpVz5CS6#k^5;7^h1&?4zem{MkUk(r!t$5N41zvx@cOXnd3~6sS-+rOla1uH0RLl?Gxn=RZ>|5CD7j5qu$VpCivTUm|i)Y`m_ zPsRna%&)JYDWA^{a{b+0aNZe){91LrycN(N@Z~m$7*mC{Ame?W&Z(GQTcu-_ml9~7 z-pbyieQo`nE)622a`T+K1C8TkdbalrG~+r;oXu&IQcSH}HMH0b|Fh z1RSBXv1KNG0rb0*Sgp{WTtX5&eJknxll0B3DWaG2Gy?7^%9XT_fC-NZzY-B7;6sPr zN1-o$`3T453=E6t51Vl5bYA@E+f5?ZGpUHA(~dm2hNg4BqsB}dn>bB?MnL1gXz>?u zT9>RQ$RI0W4SFB$Hw7G-*6P{sYUL@6LJ}Zl&@5;ew2O7Sj`o|RU`j%{wEGNRL4NUX1jPophd?R2SapflTM>iJ1}L*vX63tfLt zn=-ntaf$}eBG>|_58!AAeejjB z*MWc8X9s#t?0IW7^McF^_xGghTE$+z^dX1)YN&hkeTAc0;8(3UCkv-%_-SJ=aHnVdS=C-XKaF!oNk(@Q^Eao|!Wp*_>kBn3nLQKHXnT^3JDzOa#sJyAa`GO^kFDZYI%4%neep{-b6Z-!uwB%e?qpdABskOZ*LDIlS|wYB^6O z{kis(|ES~~|K;q|o{^lz@35!%9NKcb`|>=5_!#8yPNQ<4@lzAy4&%bjDI>v`k16_% zaGtEL-Pm-wfGKIS)}EdrP5_eHc4W{sGk6eFp3!Ny#& zc9|nu^|b-DlN7;t$WdGMQ5y;8)td4@Ar{~2y|(>s`%a3`%VkD;asPyUdaQ$e|Ehmt-|BYi^=Q>yme}w1(rSHr4r=!~PMoaMINH&Ea=%-YZS3+# zcSBp-Y@U}qTbYe1$i?n6Y8>z5T59Y+Kf2q_J`VR;S0Lqpp@FQXfQBoGm9f;=HP_wB za-jE1mlV@mR|~mrbkNmSttsyUD#xz>>8{TnJKWM}mwknpr@la{ugB+fxe;d?9fU^L%06gMT z)QbUP0kQzL0qg{L8Q=&&KfpPFF919efKGrF04V^^0Mr5;0r(o|&;l3$3IU=4aM@0P zB!Kk*F9N&`&<%j=Fbn~ln)ZDT-v0=&cn$Ch@NjGjM70QEaCxVwFR1_zJg1irL7U9N z+)d9GSW1?|%ksQ}&6`S!nN5tPxTK&sxEPkQcT%7a;P3^&&j+^8%gw@#{{n6Q3EIFV z1Ec_G_yNct{v+;rU$8Dko$dp&1sGhBq7GULxTn#J>0&bT^32)ljP>RcOI~SIlqG+A zab{uA(hPM$zB)5sok15DXQ;Pj=2^@$#(4?GoQ~^@)hX({CwMwa3NyFoXBHNgY=VD) z+01Nn@g}a2<9cgmZa&9zE6_&)&EoSl`1XVEc>v;jAiQH9#qS7sUj(pq2dFq!om-*? z540X^6v{+A;#2Gs33aOrEP2`aOG?$*=91Flf*op(9(9Sic$>Kx>czIJ;avximS18i zEG#H4HD{wdzz>(+C)>oq`*a%Nat(qXLm>0Bg?YJ5ZmGI3Gd~wRqlgoJ7=n}l;&%qT z!`1r9Z!^4m0;J_{$;{6Nbv(AC1nb3=o1a^<1+=rhIJY!6e{*qW{${h{2{Z=7sS9#)O3bBdb!mZmTW)cwB{NTro0v;V)avcIrCZcn&07nKL6m@B4LG+z z6b64g0&Rz`C>?mFkdZLqhMb&sG0e4bmwI&NJr+E#)O&U?~OZsIv+z`Pn5=>e4M{HPm8jyuZId zUsTkOQ&$Bo75T+F$F?ti=fnGwY2QV{ccNu$A=U-x0DYOt+o32&@hRefKiz=t<3DF> z=_C1;t-!ka7wTnd^)D356hT1>JYQb$oAO;AOTZrk^aCu0anw+ccV0(h&~&vw;(^LWGR?YL0%?IDbO0s6mwM6W^?J& zmV9Q5nfV#md9k_3k_+|)0?jPtq{@pM7y%n+6WrTn_`g8l*e=8*!2JniMs5Buil0I;0ZSujoT+?doN zLREroy$)@$Z&>|^PHv*$k0 z8R(1GoX_C-{cM(>U^&GFTc0k@-2QYSP+T&7hQaJC$jvW>zfJ_cCCjV^OWd4~4FuMg z{DS=D`IfxAW!x85VQE1rxPQ*m17o;^r#im?R9v7oKg*b*Z$>U|5fzned_3bH&ZunZOFLAZdY%S<l^H$PxVNJwaiE<_&^77`v}2#E+;5fT|15*iw+3)P2)g@%V3LL)*~ghuK@ zbfG$(POl5oh3gEu2;B-@q&`F+s@LiD`Y?UC-k^`ruh2(^g@lEM>B973VPWB6hOmgR z6=9L#A>pCnx^R7XSa^82Av_{{MR=qk#1LxG8T5uQL%6|Uh%l@$L`H-}ghuEh^buhZ z;Sq+2h=>&tkt;%0gs#x7(60zv5x&B(B4Wjg6_JrZVk8V738*8XTO<@M7JOVT*a%*G zL!K>Dmn~a5BW9Z(Cx*dXcddXp9stLN_r`MHPkSYntfxVZQyR+vFI}QuwNhOGlQ%d2 z9~%K@MNv^R&GhN5VEWUn;D6dw#US|6LhOm}4T9AfI4BZ=;OTP0@$>ZWm+$%B7n7oX z^k*|;?ta*pK}bC#4)240tD`ohL1a_1Da(?dpKIO}%z&vEY$`Ty&V_Yi2NyVVZZw!F zh846VIJaQAAty8 z3=2MWYi>Sh>FL7a0tSc6d_T^)s?Ej41;t40^KZ~64El;Kvvav!$|Q<&fO`Sdi|t>B z_IPc@b|g1@xc({SxF19^=~a2$Ol! z%+1cS%g#rT|2{eP-Xa(J5-aobNFmj{))) zF?j{d&tTEvYadI96=aKJ0Sx((DeAQaFk=gfV=}Xic#XvC4}RyjLXTQXAI%Z@z}umO ze3-}ycwsHJ6qc?nC^g|-!eoI^&B^mIE|8w=0+`Md|HdVB? z?9|+?W(Y>Oq4G2HIHGxk>nvuA*#rwZuO>if+y>hxEVP7to)q5PAA{YJIh~8r!7PM2 zLAQKE*mbAn|17^?dp?KX$Y2AHHG|K}2EJp=5C8$r+|t+r3t%^a$A~p&SvDu+1G$^= z-cdlBl)E*zltaM_PY~yH{Um`Mt_Q2y!4puj9&!|0%>vu^=&28EZ1to)C&aEbZx>CJ zvqcX)K2Hi1@n{d7%q?^NE9=eSBl_|>T&r2yhp=%2MkJp*#J29!!a&?$9ruI zxDNNhb@&^{nwVeQM(l%e;eK3v3{U)x+v4wMGg8#g1MC8*2XFxV2H+il0f3JI#sF>r upiJ1~1E>L(0E7Xo2DkvZh%|UI07U@L0qg{L5x@cP8bB9-3m_U|kN*Yxs!D"] +edition = "2021" +description = "Chat UI test routines" + +# Dependency versions enforced by Cargo.lock. +[dependencies] +log = "0.4.14" +num-derive = { version = "0.3.3", default-features = false} +num-traits = { version = "0.2.14", default-features = false} +xous = "0.9.54" +xous-ipc = "0.9.54" +log-server = { package = "xous-api-log", version = "0.1.50" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.50" } +xous-names = { package = "xous-api-names", version = "0.9.52" } +gam = { path = "../../services/gam" } +graphics-server = { path = "../../services/graphics-server" } +trng = { path = "../../services/trng"} +locales = { path = "../../locales"} +ime-plugin-api = { path = "../../services/ime-plugin-api"} +ime-plugin-shell = { path = "../../services/ime-plugin-shell"} +content-plugin-api = { path = "../../services/content-plugin-api"} # all content canvas providers must provide this API +codec = { path = "../../services/codec"} +com = { path = "../../services/com"} +com_rs = { git = "https://github.com/betrusted-io/com_rs", rev = "891bdd3ca8e41f81510d112483e178aea3e3a921" } +llio = { path = "../../services/llio"} +net = { path = "../../services/net" } +pddb = { path = "../../services/pddb" } +# new dependencies for mtxcli +chat = { path = "../../libs/chat" } +modals = { path = "../../services/modals" } +percent-encoding = "2.2" +rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} +serde = { version = "1.0", features = [ "derive" ] } +tls = { path = "../../libs/tls" } +ureq = { version = "2.7.1", features = ["json"] } +url = "2.2.2" + +[features] +default = [] diff --git a/apps/chat-test/README.md b/apps/chat-test/README.md new file mode 100644 index 000000000..6802197e1 --- /dev/null +++ b/apps/chat-test/README.md @@ -0,0 +1,5 @@ +# chat-test + +Routines for testing aspects of the chat application APIs. + +Not suitable for human consumption, side effects include confusion, frustration, and panic attacks. \ No newline at end of file diff --git a/apps/chat-test/src/api.rs b/apps/chat-test/src/api.rs new file mode 100644 index 000000000..84995111f --- /dev/null +++ b/apps/chat-test/src/api.rs @@ -0,0 +1,32 @@ +#[allow(dead_code)] +pub(crate) const SERVER_NAME_MTXCHAT: &str = "_Matrix chat_"; + +#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] +pub enum ChatTestOp { + /// chat ui event + Event = 0, + /// menu item click + Menu, + /// chat ui user post + Post, + /// chat ui keystroke + Rawkeys, + /// exit the application + Quit, +} + +#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] +pub enum MenuOp { + Login, + Logout, + Noop, + Room, +} + +#[allow(dead_code)] +pub struct Msg { + pub type_: String, + pub body: Option, + pub sender: Option, + pub ts: Option, +} diff --git a/apps/chat-test/src/main.rs b/apps/chat-test/src/main.rs new file mode 100644 index 000000000..797bbc83e --- /dev/null +++ b/apps/chat-test/src/main.rs @@ -0,0 +1,112 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] + +mod api; +mod tests; + +use api::*; +use chat::{Chat, Event, POST_TEXT_MAX}; +use num_traits::*; +use xous_ipc::Buffer; + +fn main() -> ! { + let stack_size = 1024 * 1024; + std::thread::Builder::new() + .stack_size(stack_size) + .spawn(wrapped_main) + .unwrap() + .join() + .unwrap() +} + +fn wrapped_main() -> ! { + log_server::init_wait().unwrap(); + log::set_max_level(log::LevelFilter::Info); + log::info!("my PID is {}", xous::process::id()); + + const HEAP_LARGER_LIMIT: usize = 2048 * 1024; + let new_limit = HEAP_LARGER_LIMIT; + let result = xous::rsyscall(xous::SysCall::AdjustProcessLimit( + xous::Limits::HeapMaximum as usize, + 0, + new_limit, + )); + + if let Ok(xous::Result::Scalar2(1, current_limit)) = result { + xous::rsyscall(xous::SysCall::AdjustProcessLimit( + xous::Limits::HeapMaximum as usize, + current_limit, + new_limit, + )) + .unwrap(); + log::info!("Heap limit increased to: {}", new_limit); + } else { + panic!("Unsupported syscall!"); + } + + let xns = xous_names::XousNames::new().unwrap(); + let sid = xns + .register_name(SERVER_NAME_MTXCHAT, None) + .expect("can't register server"); + log::trace!("registered with NS -- {:?}", sid); + + let chat = Chat::new( + gam::APP_NAME_CHAT_TEST, + gam::APP_MENU_0_CHAT_TEST, + Some(xous::connect(sid).unwrap()), + Some(ChatTestOp::Post as usize), + Some(ChatTestOp::Event as usize), + Some(ChatTestOp::Rawkeys as usize), + ); + // not used yet, but probably will use in the future + let _cid = xous::connect(sid).unwrap(); + + tests::test_ui(&chat); + loop { + let msg = xous::receive_message(sid).unwrap(); + log::debug!("got message {:?}", msg); + match FromPrimitive::from_usize(msg.body.id()) { + Some(ChatTestOp::Event) => { + log::info!("got Chat UI Event"); + xous::msg_scalar_unpack!(msg, event_code, _, _, _, { + match FromPrimitive::from_usize(event_code) { + Some(Event::Focus) => { + chat.redraw(); + } + _ => (), + } + }); + } + Some(ChatTestOp::Menu) => { + log::info!("got Chat Menu Click"); + xous::msg_scalar_unpack!(msg, menu_code, _, _, _, { + let code: Option = FromPrimitive::from_usize(menu_code); + log::info!("Got menu code {:?}", code); + }); + } + Some(ChatTestOp::Post) => { + let buffer = + unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; + let s = buffer + .to_original::, _>() + .unwrap(); + if s.len() > 0 { + // capture input instead of calling here, so message can drop and calling server is released + log::info!("got Post {:?}", s.to_string()); + } + } + Some(ChatTestOp::Rawkeys) => log::info!("got rawkeys"), + Some(ChatTestOp::Quit) => { + log::error!("got Quit"); + break; + } + _ => (), + } + } + // clean up our program + log::error!("main loop exit, destroying servers"); + xns.unregister_server(sid).unwrap(); + xous::destroy_server(sid).unwrap(); + log::trace!("quitting"); + xous::terminate_process(0) +} diff --git a/apps/chat-test/src/tests.rs b/apps/chat-test/src/tests.rs new file mode 100644 index 000000000..283688f07 --- /dev/null +++ b/apps/chat-test/src/tests.rs @@ -0,0 +1,30 @@ +use chat::Chat; + +pub fn test_ui(chat: &Chat) { + let pddb = pddb::Pddb::new(); + // nuke any existing test dictionary, if it exists + pddb.delete_dict("tests.ui", None).ok(); + pddb.sync().ok(); + + // re-create the test room from scratch every time + chat.dialogue_set("tests.ui", Some("test_room")).expect("couldn't set dialog"); + + // generate a list of test posts that are repeatable each time + chat.post_add("alice", 1_700_000_000, "hello world!", None).ok(); + chat.post_add("bob", 1_700_000_002, "hi alice!", None).ok(); + for i in 0..5 { + chat.post_add("alice", 1_700_000_005 + i*4, &format!("alice sez {}", i), None).ok(); + chat.post_add("bob", 1_700_000_006 + i*4, &format!("bob sez {}", i), None).ok(); + chat.post_add("trent", 1_700_000_007 + i*4, &format!("trent sez {}", i), None).ok(); + } + chat.post_add("alice", 1_700_001_000, "eom", None).ok(); + + log::info!("triggering save"); + xous::send_message( + chat.cid(), + xous::Message::new_scalar(chat::ChatOp::DialogueSave as usize, 0, 0, 0, 0), + ) + .expect("failed to send new inbound msgs"); + + log::info!("test setup done"); +} diff --git a/apps/hello/Cargo.toml b/apps/hello/Cargo.toml index e3e6c40ef..d53e77f6f 100644 --- a/apps/hello/Cargo.toml +++ b/apps/hello/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } gam = {path = "../../services/gam" } graphics-server = {path = "../../services/graphics-server" } locales = {path = "../../locales"} diff --git a/apps/manifest.json b/apps/manifest.json index cac2b6a1d..6e00842f5 100644 --- a/apps/manifest.json +++ b/apps/manifest.json @@ -1,4 +1,14 @@ { + "app-loader": { + "context_name": "App Loader", + "menu_name": { + "appmenu.app-loader": { + "en": "App Loader", + "en-tts": "App Loader" + } + }, + "submenu": 2 + }, "ball": { "context_name": "ball demo app", "menu_name": { @@ -44,6 +54,15 @@ }, "submenu": 1 }, + "chat-test": { + "context_name": "Chat UI test", + "menu_name": { + "appmenu.chat-test": { + "en": "Chat UI test" + } + }, + "submenu": 1 + }, "repl": { "context_name": "repl demo app", "menu_name": { diff --git a/apps/mtxchat/Cargo.toml b/apps/mtxchat/Cargo.toml index ab5aa7843..134ecdc78 100644 --- a/apps/mtxchat/Cargo.toml +++ b/apps/mtxchat/Cargo.toml @@ -10,11 +10,11 @@ description = "Matrix chat" log = "0.4.14" num-derive = { version = "0.3.3", default-features = false} num-traits = { version = "0.2.14", default-features = false} -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } gam = { path = "../../services/gam" } graphics-server = { path = "../../services/graphics-server" } trng = { path = "../../services/trng"} @@ -33,7 +33,6 @@ chat = { path = "../../libs/chat" } modals = { path = "../../services/modals" } percent-encoding = "2.2" rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} -rustls = { version = "0.21.7" } serde = { version = "1.0", features = [ "derive" ] } tls = { path = "../../libs/tls" } ureq = { version = "2.7.1", features = ["json"] } diff --git a/apps/mtxchat/locales/i18n.json b/apps/mtxchat/locales/i18n.json index e7d1166c2..2621fd2c9 100644 --- a/apps/mtxchat/locales/i18n.json +++ b/apps/mtxchat/locales/i18n.json @@ -1,4 +1,32 @@ { + "mtxchat.busy.connecting": { + "en": "Connecting...", + "en-tts": "Connecting..." + }, + "mtxchat.busy.updating": { + "en": "Updating...", + "en-tts": "Updating..." + }, + "mtxchat.busy.sending": { + "en": "Sending...", + "en-tts": "Sending..." + }, + "mtxchat.busy.room_id": { + "en": "Getting room ID...", + "en-tts": "Getting room ID..." + }, + "mtxchat.busy.login": { + "en": "Logging in...", + "en-tts": "Logging in..." + }, + "mtxchat.busy.rx_events": { + "en": "Receiving events...", + "en-tts": "Receiving events..." + }, + "mtxchat.busy.new_listen": { + "en": "Syncing all events from server...", + "en-tts": "Syncing all events from server..." + }, "mtxchat.clock.warning": { "en": "WARNING: clock not set", "en-tts": "WARNING: clock not set", @@ -6,6 +34,10 @@ "ja": "WARNING: clock not set *EN*", "zh": "WARNING: clock not set *EN*" }, + "mtxchat.status.default" : { + "en": "Use ← for login menu", + "en-tts": "Use ← for login menu" + }, "mtxchat.filter.failed": { "en": "error: could not create filter", "en-tts": "error: could not create filter", @@ -225,9 +257,9 @@ "ja": "WiFi not connected. Entering read-only mode *EN*", "zh": "WiFi not connected. Entering read-only mode *EN*" }, - "mtxchat.listen.patience": { - "en": "It takes a while to get new events from the [matrix] server.\nPlease be patient :-)", - "en-tts": "It takes a while to get new events from the [matrix] server.\nPlease be patient." + "mtxchat.wifi.warning_status": { + "en": "No connection! Read-only.", + "en-tts": "No connection! Read-only." }, "mtxchat.close.item": { "en": "Close menu", diff --git a/apps/mtxchat/src/api.rs b/apps/mtxchat/src/api.rs index 928450532..08609cca1 100644 --- a/apps/mtxchat/src/api.rs +++ b/apps/mtxchat/src/api.rs @@ -1,5 +1,5 @@ #[allow(dead_code)] -pub(crate) const SERVER_NAME_MTXCHAT: &str = "_Matrix chat_"; +pub(crate) const SERVER_NAME_MTXCHAT: &str = "_Chat UI test_"; #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] pub enum MtxchatOp { diff --git a/apps/mtxchat/src/lib.rs b/apps/mtxchat/src/lib.rs index bee9672b2..e2fdaa1a8 100644 --- a/apps/mtxchat/src/lib.rs +++ b/apps/mtxchat/src/lib.rs @@ -9,6 +9,7 @@ use listen::listen; use locales::t; use modals::Modals; use pddb::Pddb; +use ticktimer_server::Ticktimer; use std::fmt::Write as _; use std::io::{Error, ErrorKind, Read, Write as StdWrite}; use std::sync::Arc; @@ -35,8 +36,9 @@ const USER_DOMAIN_KEY: &str = "user_domain"; const DOMAIN_MATRIX: &str = "matrix.org"; -const MTX_LONG_TIMEOUT: i32 = 60000; // ms -const WIFI_TIMEOUT: u32 = 10; // seconds +const MTX_LONG_TIMEOUT_MS: i32 = 60000; // ms +const WIFI_TIMEOUT_MS: u32 = 30_000; // ms +const SEND_RETRIES: usize = 3; pub const CLOCK_NOT_SET_ID: usize = 1; pub const PDDB_NOT_MOUNTED_ID: usize = 2; @@ -83,6 +85,7 @@ pub struct MtxChat<'a> { modals: Modals, new_username: bool, new_room: bool, + tt: Ticktimer, } impl<'a> MtxChat<'a> { pub fn new(chat: &Chat) -> MtxChat { @@ -91,6 +94,8 @@ impl<'a> MtxChat<'a> { let trng = Trng::new(&xns).unwrap(); let pddb = pddb::Pddb::new(); pddb.try_mount(); + let status = t!("mtxchat.status.default", locales::LANG).to_owned(); + chat.set_status_text(&status); MtxChat { chat: chat, trng: trng, @@ -113,6 +118,7 @@ impl<'a> MtxChat<'a> { modals: modals, new_username: false, new_room: false, + tt: ticktimer_server::Ticktimer::new().unwrap(), } } @@ -248,9 +254,8 @@ impl<'a> MtxChat<'a> { self.listen(); if self.new_room { self.new_room = false; - self.modals - .show_notification(t!("mtxchat.listen.patience", locales::LANG), None) - .expect("notification failed"); + self.chat.set_status_text(t!("mtxchat.busy.new_listen", locales::LANG)); + self.chat.set_busy_state(true); } return true; } else { @@ -277,6 +282,8 @@ impl<'a> MtxChat<'a> { } pub fn login(&mut self) -> bool { + self.chat.set_status_text(t!("mtxchat.busy.login", locales::LANG)); + self.chat.set_busy_state(true); self.token = self.get(TOKEN_KEY).unwrap_or(None); self.logged_in = false; @@ -335,6 +342,7 @@ impl<'a> MtxChat<'a> { self.unset(USER_DOMAIN_KEY) .expect("failed to unset user domain"); } + self.chat.set_busy_state(false); self.logged_in } @@ -433,12 +441,16 @@ impl<'a> MtxChat<'a> { (true, Some(token), Some(user_domain), Some(room_alias)) => { let mut url = Url::parse("https://matrix.org").unwrap(); url.set_host(Some(user_domain)).expect("failed to set host"); + self.chat.set_status_text(t!("mtxchat.busy.room_id", locales::LANG)); + self.chat.set_busy_state(true); if let Some(room_id) = web::get_room_id(&mut url, &room_alias, &token, &mut self.agent) { self.set_debug(ROOM_ID_KEY, &room_id); + self.chat.set_busy_state(false); return Some(room_id); } else { + self.chat.set_busy_state(false); "failed to get room_id" } } @@ -626,14 +638,25 @@ impl<'a> MtxChat<'a> { &self.room_id, ) { (true, Some(token), Some(user_domain), Some(room_id)) => { + self.chat.set_status_text(t!("mtxchat.busy.sending", locales::LANG)); + self.chat.set_busy_state(true); log::info!("txn_id = {}", txn_id); let mut url = Url::parse("https://matrix.org").unwrap(); url.set_host(Some(user_domain)).expect("failed to set host"); - if web::send_message(&mut url, &room_id, &text, &txn_id, token, &mut self.agent) { + let mut success = false; + for _ in 0..SEND_RETRIES { + if web::send_message(&mut url, &room_id, &text, &txn_id, token, &mut self.agent) { + success = true; + break; + } + } + let r = if success { "SENT" } else { "FAILED TO SEND" - } + }; + self.chat.set_busy_state(false); + r } (false, _, _, _) => "Not logged in", (_, None, _, _) => "No token set", @@ -661,25 +684,22 @@ impl<'a> MtxChat<'a> { while self.wifi_try_modal() { self.netmgr.connection_manager_wifi_on_and_run().unwrap(); - self.modals - .start_progress("Connecting ...", 0, 10, 0) - .expect("no progress bar"); - let tt = ticktimer_server::Ticktimer::new().unwrap(); - for wait in 0..WIFI_TIMEOUT { - tt.sleep_ms(1000).unwrap(); - self.modals - .update_progress(wait) - .expect("no progress update"); + self.chat.set_status_text(t!("mtxchat.busy.connecting", locales::LANG)); + self.chat.set_busy_state(true); + let start = self.tt.elapsed_ms(); + while self.tt.elapsed_ms() - start < WIFI_TIMEOUT_MS as u64 { if let Some(conf) = self.netmgr.get_ipv4_config() { if conf.dhcp == com_rs::DhcpState::Bound { - self.modals - .finish_progress() - .expect("failed progress finish"); + self.chat.set_busy_state(false); return true; } } + self.tt.sleep_ms(1000).unwrap(); } + self.modals.show_notification(t!("mtxchat.wifi.warning", locales::LANG), None).unwrap(); } + self.chat.set_busy_state(false); + self.chat.set_status_text(t!("mtxchat.wifi.warning_status", locales::LANG)); false } diff --git a/apps/mtxchat/src/listen.rs b/apps/mtxchat/src/listen.rs index 51bb325cf..4e4874919 100644 --- a/apps/mtxchat/src/listen.rs +++ b/apps/mtxchat/src/listen.rs @@ -1,11 +1,11 @@ -use crate::{get_username, web, MTX_LONG_TIMEOUT}; +use crate::{get_username, web, MTX_LONG_TIMEOUT_MS}; use chat::ChatOp; -use modals::Modals; use std::sync::Arc; use tls::xtls::TlsConnector; use url::Url; use xous::CID; use xous_ipc::Buffer; +use locales::t; pub fn listen( url: &mut Url, @@ -16,9 +16,7 @@ pub fn listen( dialogue_id: &str, chat_cid: CID, ) { - let xns = xous_names::XousNames::new().unwrap(); - let modals = Modals::new(&xns).expect("can't connect to Modals server"); - log::info!("client_sync for {} ms...", MTX_LONG_TIMEOUT); + log::info!("client_sync for {} ms...", MTX_LONG_TIMEOUT_MS); let mut agent = ureq::builder() .tls_connector(Arc::new(TlsConnector{})) @@ -27,7 +25,7 @@ pub fn listen( url, filter, since, - MTX_LONG_TIMEOUT, + MTX_LONG_TIMEOUT_MS, &room_id, &token, &mut agent, @@ -38,9 +36,8 @@ pub fn listen( // TODO resolve suspected race condition // This progress modal is masking a bug by slowing the loop down // Precursor "Guru Mediation" `voilated: nonNull::new_unchecked` - modals - .start_progress("Receiving events ...", 0, events.len() as u32, 0) - .expect("no progress bar"); + chat::cf_set_status_text(chat_cid, t!("mtxchat.busy.rx_events", locales::LANG)); + chat::cf_set_busy_state(chat_cid, true); let mut event_count = 0; for event in events { let sender = event.sender.unwrap_or("anon".to_string()); @@ -58,12 +55,14 @@ pub fn listen( } .expect("failed to convert post into buffer"); event_count += 1; - modals - .update_progress(event_count) - .expect("no progress update"); + chat::cf_set_status_text(chat_cid, + &format!("{} {}", + t!("mtxchat.busy.rx_events", locales::LANG), + event_count) + ); } } - modals.finish_progress().expect("failed progress finish"); + chat::cf_set_busy_state(chat_cid, false); // trigger the chat ui to save the dialogue to the pddb xous::send_message( chat_cid, diff --git a/apps/mtxchat/src/main.rs b/apps/mtxchat/src/main.rs index 0b7ea6ef3..e2b0dc668 100644 --- a/apps/mtxchat/src/main.rs +++ b/apps/mtxchat/src/main.rs @@ -2,6 +2,7 @@ #![cfg_attr(target_os = "none", no_main)] mod api; + use api::*; use chat::{Chat, Event, POST_TEXT_MAX}; use gam::{MenuItem, MenuPayload}; diff --git a/apps/mtxcli/Cargo.toml b/apps/mtxcli/Cargo.toml index 3f52005fe..68551fb4d 100644 --- a/apps/mtxcli/Cargo.toml +++ b/apps/mtxcli/Cargo.toml @@ -10,11 +10,11 @@ description = "Matrix chat" log = "0.4.14" num-derive = { version = "0.3.3", default-features = false} num-traits = { version = "0.2.14", default-features = false} -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } gam = { path = "../../services/gam" } graphics-server = { path = "../../services/graphics-server" } trng = { path = "../../services/trng"} diff --git a/apps/mtxcli/locales/i18n.json b/apps/mtxcli/locales/i18n.json index 774c4128c..fb0eb3f57 100644 --- a/apps/mtxcli/locales/i18n.json +++ b/apps/mtxcli/locales/i18n.json @@ -1,216 +1,222 @@ { - "mtxchat.clock.warning": { + "mtxcli.clock.warning": { "en": "WARNING: clock not set", "en-tts": "WARNING: clock not set", "fr": "AVERTISSEMENT : horloge non réglée", "ja": "WARNING: clock not set *EN*", "zh": "WARNING: clock not set *EN*" }, - "mtxchat.filter.failed": { + "mtxcli.filter.failed": { "en": "error: could not create filter", "en-tts": "error: could not create filter", "fr": "erreur: impossible de créer un filtre *MT*", "ja": "error: could not create filter *EN*", "zh": "error: could not create filter *EN*" }, - "mtxchat.get.help": { + "mtxcli.get.help": { "en": "/get key", "en-tts": "get key", "fr": "/get clé", "ja": "/get key *EN*", "zh": "/get key *EN*" }, - "mtxchat.greeting": { - "en": "Welcome to the MTXCHAT demo. Type /help for available commands", - "en-tts": "Welcome to the simple MTXCHAT demo. Type /help for available commands", - "fr": "Bienvenue à la démo simple de MTXCHAT. Tapez /help pour les commandes disponibles", - "ja": "シンプルなMTXCHATデモへようこそ", - "zh": "简单的 mtxchat 演示活动" + "mtxcli.greeting": { + "en": "Welcome to the MTXCLI demo. Type /help for available commands", + "en-tts": "Welcome to the simple MTXCLI demo. Type /help for available commands", + "fr": "Bienvenue à la démo simple de MTXCLI. Tapez /help pour les commandes disponibles", + "ja": "シンプルなMTXCLIデモへようこそ", + "zh": "简单的 mtxcli 演示活动" }, - "mtxchat.heap.help": { + "mtxcli.heap.help": { "en": "/heap", "en-tts": "heap", "fr": "/heap", "ja": "/heap", "zh": "/heap" }, - "mtxchat.help.help": { + "mtxcli.help.help": { "en": "/help [cmd]", "en-tts": "help [cmd]", "fr": "/help [cmd]", "ja": "/help [cmd] *EN*", "zh": "/help [cmd] *EN*" }, - "mtxchat.help.overview": { + "mtxcli.help.overview": { "en": "available mtxcli commands:", "en-tts": "available mtxcli commands:", "fr": "commandes mtxcli disponibles:", "ja": "available mtxcli commands: *EN*", "zh": "available mtxcli commands: *EN*" }, - "mtxchat.initialized": { - "en": "MTXCHAT ready", - "en-tts": "MTXCHAT ready", - "fr": "MTXCHAT prêt", - "ja": "MTXCHAT ready *EN*", - "zh": "MTXCHAT ready *EN*" + "mtxcli.initialized": { + "en": "MTXCLI ready", + "en-tts": "MTXCLI ready", + "fr": "MTXCLI prêt", + "ja": "MTXCLI ready *EN*", + "zh": "MTXCLI ready *EN*" }, - "mtxchat.logged.in": { + "mtxcli.logged.in": { "en": "logged in", "en-tts": "logged in", "fr": "connecté", "ja": "logged in *EN*", "zh": "logged in *EN*" }, - "mtxchat.logged.out": { + "mtxcli.logged.out": { "en": "logged out", "en-tts": "logged out", "fr": "déconnecté", "ja": "logged out *EN*", "zh": "logged out *EN*" }, - "mtxchat.logging.in": { + "mtxcli.logging.in": { "en": "logging in...", "en-tts": "logging in", "fr": "connexion...", "ja": "logging in... *EN*", "zh": "logging in... *EN*" }, - "mtxchat.login.failed": { + "mtxcli.login.failed": { "en": "authentication failed", "en-tts": "authentication failed", "fr": "échec de l'authentification", "ja": "authentication failed *EN*", "zh": "authentication failed *EN*" }, - "mtxchat.login.help": { + "mtxcli.login.help": { "en": "/login", "en-tts": "login", "fr": "/login (connexion)", "ja": "/login *EN*", "zh": "/login *EN*" }, - "mtxchat.logout.help": { + "mtxcli.logout.help": { "en": "/logout", "en-tts": "logout", "fr": "/logout (déconnexion)", "ja": "/logout *EN*", "zh": "/logout *EN*" }, - "mtxchat.migration.completed": { + "mtxcli.migration.completed": { "en": "migration completed", "en-tts": "migration completed", "fr": "migration terminée", "ja": "migration completed *EN*", "zh": "migration completed *EN*" }, - "mtxchat.migrations.complete": { + "mtxcli.migrations.complete": { "en": "migrations complete", "en-tts": "migrations complete", "fr": "tous les migrations terminées", "ja": "migrations complete *EN*", "zh": "migrations complete *EN*" }, - "mtxchat.not.connected": { + "mtxcli.not.connected": { "en": "not connected", "en-tts": "not conntected", "fr": "déconnecté", "ja": "not connected *EN*", "zh": "not connected *EN*" }, - "mtxchat.pddb.warning": { + "mtxcli.pddb.warning": { "en": "WARNING: pddb not mounted", "en-tts": "WARNING: pddb not mounted", "fr": "AVERTISSEMENT: pddb pas monté", "ja": "WARNING: pddb not mounted *EN*", "zh": "WARNING: pddb not mounted *EN*" }, - "mtxchat.login.title": { - "en": "Matrix /login", - "en-tts": "Matrix /login" + "mtxcli.please.login": { + "en": "please /login", + "en-tts": "please /login", + "fr": "Veuillez /login", + "ja": "please /login *EN*", + "zh": "please /login *EN*" }, - "mtxchat.password": { - "en": "password", - "en-tts": "password", - "fr": "de-passe", - "ja": "password *EN*", - "zh": "password *EN*" + "mtxcli.please.set.password": { + "en": "please /set password my-password", + "en-tts": "please /set password my-password", + "fr": "Veuillez /set password mot-de-passe", + "ja": "please /set password my-password *EN*", + "zh": "please /set password my-password *EN*" }, - "mtxchat.please.set.room": { + "mtxcli.please.set.room": { "en": "please /set room #my-room-to-join", "en-tts": "please /set room #my-room-to-join", "fr": "Veuillez /set room #ma-chambre", "ja": "please /set room #my-room-to-join *EN*", "zh": "please /set room #my-room-to-join *EN*" }, - "mtxchat.server": { - "en": "server", - "en-tts": "server" + "mtxcli.please.set.server": { + "en": "please /set server https://matrix.org", + "en-tts": "please /set server https://matrix.org", + "fr": "Veuillez /set server https://matrix.org", + "ja": "please /set server https://matrix.org *EN*", + "zh": "please /set server https://matrix.org *EN*" }, - "mtxchat.username": { - "en": "username", - "en-tts": "username", - "fr": "utilisateur", - "ja": "username *EN*", - "zh": "username *EN*" + "mtxcli.please.set.user": { + "en": "please /set user @USER:matrix.org", + "en-tts": "please /set user @USER:matrix.org", + "fr": "Veuillez /set user @UTILISATEUR:matrix.org", + "ja": "please /set user @USER:matrix.org *EN*", + "zh": "please /set user @USER:matrix.org *EN*" }, - "mtxchat.roomid.failed": { + "mtxcli.roomid.failed": { "en": "error: could not find room_id", "en-tts": "error: could not find room_id", "fr": "erreur: impossible de trouver chambre", "ja": "error: could not find room_id *EN*", "zh": "error: could not find room_id *EN*" }, - "mtxchat.running.migrations": { + "mtxcli.running.migrations": { "en": "Running migrations from version", "en-tts": "Running migrations from version", "fr": "Exécution des migrations à partir de la version", "ja": "Running migrations from version *EN*", "zh": "Running migrations from version *EN*" }, - "mtxchat.send.failed": { + "mtxcli.send.failed": { "en": "failed to send!", "en-tts": "failed to send!", "fr": "n'a pas envoyé!", "ja": "failed to send! *EN*", "zh": "failed to send! *EN*" }, - "mtxchat.set.help": { + "mtxcli.set.help": { "en": "/set key value", "en-tts": "set key value", "fr": "/set clé valeur", "ja": "/set key value *EN*", "zh": "/set key value *EN*" }, - "mtxchat.status.help": { + "mtxcli.status.help": { "en": "/status", "en-tts": "status", "fr": "/status (statut)", "ja": "/status *EN*", "zh": "/status *EN*" }, - "mtxchat.unknown.help": { + "mtxcli.unknown.help": { "en": "unknown command", "en-tts": "unknown command", "fr": "commande inconnue", "ja": "unknown command *EN*", "zh": "unknown command *EN*" }, - "mtxchat.unset.help": { + "mtxcli.unset.help": { "en": "/unset key", "en-tts": "unset key", "fr": "/unset clé", "ja": "/unset key *EN*", "zh": "/unset key *EN*" }, - "mtxchat.wifi.connected": { + "mtxcli.wifi.connected": { "en": "WiFi connected", "en-tts": "WiFi connected", "fr": "WiFi connecté", "ja": "WiFi connected *EN*", "zh": "WiFi connected *EN*" }, - "mtxchat.wifi.warning": { + "mtxcli.wifi.warning": { "en": "WARNING: WiFi not connected", "en-tts": "WARNING: WiFi not connected", "fr": "AVERTISSEMENT: WiFi pas connecté", diff --git a/apps/repl/Cargo.toml b/apps/repl/Cargo.toml index 3739a7348..144c03a62 100644 --- a/apps/repl/Cargo.toml +++ b/apps/repl/Cargo.toml @@ -10,11 +10,11 @@ description = "REPL demo app" log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } gam = {path = "../../services/gam" } graphics-server = {path = "../../services/graphics-server" } trng = {path = "../../services/trng"} diff --git a/apps/transientdisk/Cargo.toml b/apps/transientdisk/Cargo.toml index 574a868b7..e89693eb0 100644 --- a/apps/transientdisk/Cargo.toml +++ b/apps/transientdisk/Cargo.toml @@ -9,10 +9,10 @@ edition = "2021" log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } gam = {path = "../../services/gam" } graphics-server = {path = "../../services/graphics-server" } usb-device-xous = {path="../../services/usb-device-xous"} diff --git a/apps/vault/Cargo.toml b/apps/vault/Cargo.toml index 5063c4661..a7abb39be 100644 --- a/apps/vault/Cargo.toml +++ b/apps/vault/Cargo.toml @@ -7,21 +7,21 @@ edition = "2018" log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -xous = "0.9.53" -xous-ipc = "0.9.53" +xous = "0.9.55" +xous-ipc = "0.9.55" rkyv = {version = "0.4.3", features = ["const_generics"], default-features = false} -log-server = { package = "xous-api-log", version = "0.1.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +log-server = { package = "xous-api-log", version = "0.1.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } gam = {path = "../../services/gam" } graphics-server = {path = "../../services/graphics-server" } locales = {path = "../../locales"} tts-frontend = {path="../../services/tts"} usb-device-xous = {path="../../services/usb-device-xous"} -usbd-human-interface-device = "0.2.1" +xous-usb-hid = {git = "https://github.com/betrusted-io/xous-usb-hid.git"} pddb = {path = "../../services/pddb" } modals = {path = "../../services/modals" } trng = {path="../../services/trng"} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} ime-plugin-api = {path = "../../services/ime-plugin-api"} content-plugin-api = {path = "../../services/content-plugin-api"} # all content canvas providers must provide this API backup = {path = "libraries/backup"} @@ -29,7 +29,7 @@ byteorder = { version = "1.4.3", default-features = false } arrayref = "0.3.6" subtle = { version = "2.5.0", features = ["core_hint_black_box"] } rand_core = "0.6.3" -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} userprefs = {path = "../../libs/userprefs"} # opensk diff --git a/apps/vault/libraries/crypto/Cargo.toml b/apps/vault/libraries/crypto/Cargo.toml index 8322308e6..c7995b340 100644 --- a/apps/vault/libraries/crypto/Cargo.toml +++ b/apps/vault/libraries/crypto/Cargo.toml @@ -14,7 +14,7 @@ cbor = { path = "../cbor" } arrayref = "0.3.6" subtle = { version = "2.2.3", default-features = false} trng = {path = "../../../../services/trng"} -xous-names = {package = "xous-api-names", version = "0.9.51"} +xous-names = {package = "xous-api-names", version = "0.9.53"} rand_core = "0.6.3" p256 = {version = "0.10.1", default-features = false, features = ["ecdsa", "ecdh", "std"]} cbc = "0.1.2" diff --git a/apps/vault/locales/i18n.json b/apps/vault/locales/i18n.json index ea86bce41..16b2a587f 100644 --- a/apps/vault/locales/i18n.json +++ b/apps/vault/locales/i18n.json @@ -363,6 +363,13 @@ "ja": "これはどのタイプの OTP レコードですか (わからない場合は、おそらく TOTP です)。", "zh": "这是哪种类型的 OTP 记录(如果您不知道,可能是 TOTP)?" }, + "vault.newitem.lowercase": { + "en": "Lowercase letters", + "en-tts": "Lowercase letters", + "fr": "Lettres minuscules", + "ja": "小文字", + "zh": "小写字母" + }, "vault.newitem.name": { "en": "Please name the credential:", "en-tts": "Please name the credential", diff --git a/apps/vault/src/actions.rs b/apps/vault/src/actions.rs index d3301dafd..bf2c35856 100644 --- a/apps/vault/src/actions.rs +++ b/apps/vault/src/actions.rs @@ -248,32 +248,40 @@ impl<'a> ActionManager<'a> { let mut upper = false; let mut number = false; let mut symbol = false; - self.modals - .add_list(vec![ - t!("vault.newitem.uppercase", locales::LANG), - t!("vault.newitem.numbers", locales::LANG), - t!("vault.newitem.symbols", locales::LANG), - ]).expect("couldn't create configuration modal"); - match self.modals.get_checkbox(t!("vault.newitem.configure_generator", locales::LANG)) { - Ok(options) => { - for opt in options { - if opt == t!("vault.newitem.uppercase", locales::LANG) {upper = true;} - if opt == t!("vault.newitem.numbers", locales::LANG) {number = true;} - if opt == t!("vault.newitem.symbols", locales::LANG) {symbol = true;} + let mut lower = false; + while !upper && !number && !symbol && !lower { + self.modals + .add_list(vec![ + t!("vault.newitem.lowercase", locales::LANG), + t!("vault.newitem.uppercase", locales::LANG), + t!("vault.newitem.numbers", locales::LANG), + t!("vault.newitem.symbols", locales::LANG), + ]).expect("couldn't create configuration modal"); + match self.modals.get_checkbox(t!("vault.newitem.configure_generator", locales::LANG)) { + Ok(options) => { + for opt in options { + if opt == t!("vault.newitem.lowercase", locales::LANG) {lower = true;} + if opt == t!("vault.newitem.uppercase", locales::LANG) {upper = true;} + if opt == t!("vault.newitem.numbers", locales::LANG) {number = true;} + if opt == t!("vault.newitem.symbols", locales::LANG) {symbol = true;} + } } + _ => {log::error!("Modal selection error"); self.action_active.store(false, Ordering::SeqCst); return} + } + if upper == false && lower == false && symbol == false && number == false { + self.modals.show_notification(t!("vault.error.nothing_selected", locales::LANG), None).ok(); } - _ => {log::error!("Modal selection error"); self.action_active.store(false, Ordering::SeqCst); return} } #[cfg(feature="ux-swap-delay")] self.tt.sleep_ms(SWAP_DELAY_MS).unwrap(); let pg2 = PasswordGenerator { length: length as usize, numbers: number, - lowercase_letters: true, + lowercase_letters: lower, uppercase_letters: upper, symbols: symbol, spaces: false, - exclude_similar_characters: true, + exclude_similar_characters: upper || lower, strict: true, }; approved = false; @@ -774,32 +782,40 @@ impl<'a> ActionManager<'a> { let mut upper = false; let mut number = false; let mut symbol = false; - self.modals - .add_list(vec![ - t!("vault.newitem.uppercase", locales::LANG), - t!("vault.newitem.numbers", locales::LANG), - t!("vault.newitem.symbols", locales::LANG), - ]).expect("couldn't create configuration modal"); - match self.modals.get_checkbox(t!("vault.newitem.configure_generator", locales::LANG)) { - Ok(options) => { - for opt in options { - if opt == t!("vault.newitem.uppercase", locales::LANG) {upper = true;} - if opt == t!("vault.newitem.numbers", locales::LANG) {number = true;} - if opt == t!("vault.newitem.symbols", locales::LANG) {symbol = true;} + let mut lower = false; + while !upper && !number && !symbol && !lower { + self.modals + .add_list(vec![ + t!("vault.newitem.lowercase", locales::LANG), + t!("vault.newitem.uppercase", locales::LANG), + t!("vault.newitem.numbers", locales::LANG), + t!("vault.newitem.symbols", locales::LANG), + ]).expect("couldn't create configuration modal"); + match self.modals.get_checkbox(t!("vault.newitem.configure_generator", locales::LANG)) { + Ok(options) => { + for opt in options { + if opt == t!("vault.newitem.lowercase", locales::LANG) {lower = true;} + if opt == t!("vault.newitem.uppercase", locales::LANG) {upper = true;} + if opt == t!("vault.newitem.numbers", locales::LANG) {number = true;} + if opt == t!("vault.newitem.symbols", locales::LANG) {symbol = true;} + } } + _ => {log::error!("Modal selection error"); self.action_active.store(false, Ordering::SeqCst); return} + } + if upper == false && lower == false && symbol == false && number == false { + self.modals.show_notification(t!("vault.error.nothing_selected", locales::LANG), None).ok(); } - _ => {log::error!("Modal selection error"); self.action_active.store(false, Ordering::SeqCst); return} } #[cfg(feature="ux-swap-delay")] self.tt.sleep_ms(SWAP_DELAY_MS).unwrap(); let pg2 = PasswordGenerator { length: length as usize, numbers: number, - lowercase_letters: true, + lowercase_letters: lower, uppercase_letters: upper, symbols: symbol, spaces: false, - exclude_similar_characters: true, + exclude_similar_characters: upper || lower, strict: true, }; approved = false; diff --git a/apps/vault/src/env/xous/mod.rs b/apps/vault/src/env/xous/mod.rs index acc182011..3a67ef37f 100644 --- a/apps/vault/src/env/xous/mod.rs +++ b/apps/vault/src/env/xous/mod.rs @@ -16,7 +16,7 @@ use ctap_crypto::rng256::XousRng256; use xous::try_send_message; use xous_names::XousNames; use crate::env::xous::storage::XousUpgradeStorage; -use usbd_human_interface_device::device::fido::*; +use xous_usb_hid::device::fido::*; use modals::Modals; use locales::t; use std::io::{Read, Write}; @@ -48,10 +48,10 @@ impl XousHidConnection { } } } - pub fn u2f_wait_incoming(&self) -> Result { + pub fn u2f_wait_incoming(&self) -> Result { self.endpoint.u2f_wait_incoming() } - pub fn u2f_send(&self, msg: RawFidoMsg) -> Result<(), xous::Error> { + pub fn u2f_send(&self, msg: RawFidoReport) -> Result<(), xous::Error> { self.endpoint.u2f_send(msg) } } @@ -62,7 +62,7 @@ impl HidConnection for XousHidConnection { buf: &mut [u8; 64], _timeout: Duration, ) -> SendOrRecvResult { - let mut reply = RawFidoMsg::default(); + let mut reply = RawFidoReport::default(); reply.packet.copy_from_slice(buf); match self.endpoint.u2f_send(reply) { Ok(()) => Ok(SendOrRecvStatus::Sent), diff --git a/apps/vault/src/main.rs b/apps/vault/src/main.rs index 653e0427c..df52cd2a5 100644 --- a/apps/vault/src/main.rs +++ b/apps/vault/src/main.rs @@ -29,7 +29,7 @@ use crate::vendor_commands::VendorSession; use ux::framework::{VaultUx, DEFAULT_FONT, FONT_LIST, name_to_style}; use xous_ipc::Buffer; use xous::{send_message, Message, msg_blocking_scalar_unpack, msg_scalar_unpack}; -use usbd_human_interface_device::device::fido::*; +use xous_usb_hid::device::fido::*; use num_traits::*; use std::thread; @@ -312,7 +312,7 @@ fn main() -> ! { match typed_reply { HidIterType::Ctap(reply) => { for pkt_reply in reply { - let mut reply = RawFidoMsg::default(); + let mut reply = RawFidoReport::default(); reply.packet.copy_from_slice(&pkt_reply); let status = ctap.env().main_hid_connection().u2f_send(reply); match status { @@ -372,7 +372,7 @@ fn main() -> ! { } }; for pkt_reply in reply { - let mut reply = RawFidoMsg::default(); + let mut reply = RawFidoReport::default(); reply.packet.copy_from_slice(&pkt_reply); let status = ctap.env().main_hid_connection().u2f_send(reply); match status { diff --git a/imports/getrandom/Cargo.toml b/imports/getrandom/Cargo.toml index faa926b54..4b766708e 100644 --- a/imports/getrandom/Cargo.toml +++ b/imports/getrandom/Cargo.toml @@ -30,9 +30,9 @@ js-sys = { version = "0.3", optional = true } wasm-bindgen-test = "0.3.18" [target.'cfg(target_os = "xous")'.dependencies] -xous-names = {package = "xous-api-names", version = "0.9.51"} -xous = "0.9.53" -xous-ipc = "0.9.53" +xous-names = {package = "xous-api-names", version = "0.9.53"} +xous = "0.9.55" +xous-ipc = "0.9.55" rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 8e193bbe9..2d3a2fbd1 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -11,14 +11,14 @@ resolver = "2" [dependencies] bitflags = "1.2.1" stats_alloc = { version = "0.1.8", optional = true } -xous-kernel = { package = "xous", version = "0.9.53", features = [ +xous-kernel = { package = "xous", version = "0.9.55", features = [ "forget-memory-messages", ] } utralib = { version = "0.1.23", optional = true, default_features = false } rand_chacha = { version = "0.3.1", optional = true, default_features = false } [target.'cfg(any(windows,unix))'.dev-dependencies] -xous-kernel = { package = "xous", version = "0.9.53", features = [ +xous-kernel = { package = "xous", version = "0.9.55", features = [ "forget-memory-messages", "processes-as-threads", ] } @@ -31,7 +31,7 @@ gdbstub_arch = { version = "0.2.4", optional = true, default-features = false } [target.'cfg(target_arch = "arm")'.dependencies] armv7 = { git = "https://github.com/Foundation-Devices/armv7.git", branch = "update", features = ["critical-section"] } atsama5d27 = { git = "https://github.com/Foundation-Devices/atsama5d27.git", branch = "master" } -xous-kernel = { package = "xous", version = "0.9.53", features = [ +xous-kernel = { package = "xous", version = "0.9.55", features = [ "v2p", ] } critical-section = "1.1.1" diff --git a/kernel/src/arch/riscv/process.rs b/kernel/src/arch/riscv/process.rs index f78fb11d4..28c4c939a 100644 --- a/kernel/src/arch/riscv/process.rs +++ b/kernel/src/arch/riscv/process.rs @@ -386,6 +386,7 @@ impl Process { } process.inner = Default::default(); + process.inner.pid = pid; // Mark the stack as "unallocated-but-free" let init_sp = (thread_init.stack.as_ptr() as usize) & !0xfff; diff --git a/libs/chat/Cargo.toml b/libs/chat/Cargo.toml index 2e0099da7..431fe7c14 100644 --- a/libs/chat/Cargo.toml +++ b/libs/chat/Cargo.toml @@ -10,11 +10,11 @@ description = "Chat UI over pddb records" log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } gam = {path = "../../services/gam" } graphics-server = {path = "../../services/graphics-server" } trng = {path = "../../services/trng"} diff --git a/libs/chat/locales/i18n.json b/libs/chat/locales/i18n.json index 21546394f..bf1caf3a4 100644 --- a/libs/chat/locales/i18n.json +++ b/libs/chat/locales/i18n.json @@ -19,6 +19,10 @@ "en": "no dialogues available to read", "en-tts": "no dialogues available to read" }, + "chat.status.initial" : { + "en": "Use ← to raise menu", + "en-tts": "Use ← to raise menu" + }, "chat.help.navigation": { "en": "use ↑ & ↓ to scroll thru old posts\nuse ← & → to show menus\nF1-F4 dont do anything yet.", "en-tts": "use ↑ & ↓ to scroll thru old posts\nuse ← & → to show menus\nF1-F4 dont do anything yet." diff --git a/libs/chat/src/api.rs b/libs/chat/src/api.rs index 2547a1892..a41891705 100644 --- a/libs/chat/src/api.rs +++ b/libs/chat/src/api.rs @@ -36,10 +36,27 @@ pub enum ChatOp { /// Find a Post by timestamp and Author PostFind, PostFlag, + /// Set status bar text + SetStatusText, + /// Run or stop the busy animation. + SetBusyAnimationState, + /// Set the status idle text (to be shown when exiting all busy states) + SetStatusIdleText, + /// Update just the state of the busy animation, if any. Internal opcode. + /// Will skip the update if called too often. + UpdateBusy, + /// Force update the busy bar, without rate throttling. Internal opcode. + UpdateBusyForced, /// exit the application Quit, } +#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] +pub(crate) enum BusyAnimOp { + Start, + Pump, +} + #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] pub enum IconOp { PostMenu = 0, @@ -120,3 +137,8 @@ pub enum AuthorFlag { Hidden, Right, } + +#[derive(Archive, Serialize, Deserialize, Debug)] +pub struct BusyMessage { + pub busy_msg: xous_ipc::String<128>, +} diff --git a/libs/chat/src/dialogue.rs b/libs/chat/src/dialogue.rs index 996fb243e..7924e9b2d 100644 --- a/libs/chat/src/dialogue.rs +++ b/libs/chat/src/dialogue.rs @@ -2,10 +2,12 @@ pub mod attach; pub mod author; pub mod post; -use crate::now; +use crate::ui::VisualProperties; +use crate::{now, default_textview}; use author::Author; -use core::slice::Iter; +use gam::Gam; +use core::slice::{Iter, IterMut}; use post::Post; use rkyv::{Archive, Deserialize, Serialize}; use std::collections::HashMap; @@ -65,6 +67,7 @@ impl Dialogue { /// * `timestamp` - the timestamp of the Post /// * `text` - the text content of the Post /// * `attach_url` - a url of an attachment (image for example) + /// * `vp` - the visual properties of the system - so that we can pre-compute the size extents of the post /// pub fn post_add( &mut self, @@ -72,10 +75,11 @@ impl Dialogue { timestamp: u64, text: &str, _attach_url: Option<&str>, + vp: Option<(&VisualProperties, &Gam)>, ) -> Result<(), Error> { match self.author_id(author) { Some(author_id) => { - let new = Post::new( + let mut new = Post::new( author_id, timestamp, text, None, // TODO implement ); if self.posts.len() == 0 { @@ -86,6 +90,16 @@ impl Dialogue { let first_ts = self.posts.first().map_or(0, |p| p.timestamp()); let last_ts = self.posts.last().map_or(0, |p| p.timestamp()); log::trace!("{:?}", new); + + // compute the bounds for the post if visual properties are specified + if let Some((vp, gam)) = vp { + let mut layout_bubble = default_textview(&new, false, vp); + log::debug!("Computing bounds on {:?}", layout_bubble); + if gam.bounds_compute_textview(&mut layout_bubble).is_ok() { + new.bounding_box = layout_bubble.bounds_computed; + } + } + if new_ts > last_ts { log::info!("insert new post at end"); self.posts.push(new); @@ -165,12 +179,27 @@ impl Dialogue { } } + /// Return a slice of posts + pub fn posts_as_slice(&self) -> &[Post] { + &self.posts + } + + pub fn posts_as_slice_mut(&mut self) -> &mut [Post] { + &mut self.posts + } + /// Return an iterator over the Dialogue Posts (oldest first) /// pub fn posts(&self) -> Iter { return self.posts.iter(); } + /// Return a mut iterator over the Dialogue Posts (oldest first) + /// + pub fn posts_mut(&mut self) -> IterMut { + return self.posts.iter_mut(); + } + /// Return Some by id, or None. /// /// # Arguments diff --git a/libs/chat/src/dialogue/post.rs b/libs/chat/src/dialogue/post.rs index 0240ea68f..c7be6ee5d 100644 --- a/libs/chat/src/dialogue/post.rs +++ b/libs/chat/src/dialogue/post.rs @@ -2,6 +2,7 @@ use super::attach::Attach; use crate::PostFlag; use enumset::EnumSet; +use graphics_server::Rectangle; use rkyv::{Archive, Deserialize, Serialize}; #[derive(Archive, Serialize, Deserialize, Debug)] @@ -11,6 +12,7 @@ pub struct Post { text: String, attach: Option, pub flags: u16, + pub bounding_box: Option, } #[allow(dead_code)] @@ -22,6 +24,7 @@ impl Post { text: text.to_string(), attach: attach, flags: 0, + bounding_box: None, } } diff --git a/libs/chat/src/lib.rs b/libs/chat/src/lib.rs index e2a4b58dd..836ce40ca 100644 --- a/libs/chat/src/lib.rs +++ b/libs/chat/src/lib.rs @@ -4,15 +4,102 @@ pub mod icontray; pub mod ui; pub use api::*; +use graphics_server::{TextView, Point, Rectangle, TextBounds}; +use graphics_server::api::GlyphStyle; +pub use ui::BUSY_ANIMATION_RATE_MS; use gam::MenuItem; use num_traits::FromPrimitive; +use ui::VisualProperties; use std::convert::TryInto; use std::thread; use std::time::{SystemTime, UNIX_EPOCH}; +use std::sync::{Arc, atomic::AtomicBool, atomic::Ordering}; -use xous::{Error, CID, SID}; +use xous::{Error, CID, SID, msg_scalar_unpack}; use xous_ipc::Buffer; +/// Create a TextView with the default properties common to all text bubbles. +pub(crate) fn default_textview( + post: &crate::dialogue::post::Post, + hilite: bool, + vp: &VisualProperties, +) -> TextView { + use std::fmt::Write; + let mut bubble_tv = TextView::new( + vp.canvas, + TextBounds::GrowableFromBl( + Point::new(vp.margin.x, vp.layout_screensize.y), + vp.bubble_width + ) + ); + if hilite { + bubble_tv.border_width = 3; + } else { + bubble_tv.border_width = 1; + } + bubble_tv.clip_rect = Some( + Rectangle::new( + Point::new(0, vp.status_height as i16 + vp.margin.y), + vp.layout_screensize + ) + ); + bubble_tv.draw_border = true; + bubble_tv.clear_area = true; + bubble_tv.rounded_border = Some(vp.bubble_radius); + bubble_tv.style = GlyphStyle::Regular; + bubble_tv.margin = vp.bubble_margin; + bubble_tv.ellipsis = false; + bubble_tv.insertion = None; + write!(bubble_tv.text, "{}", post.text()).expect("couldn't write history text to TextView"); + bubble_tv +} + +/// Return a TextView bubble representing a Dialogue Post +/// +/// # Arguments +/// +/// * `vp` - the visual properties to be applied to the textview +/// * `topdown` - direction of the layout +/// * `post` - the post to represent in a TextView bubble +/// * `dialogue` - containing the Post for context info +/// * `hilite` - hilite this Post on the screen (thicker border) +/// * `anchor_y` - the vertical position on screen to draw TextView bubble +/// +fn bubble( + vp: &VisualProperties, + topdown: bool, + post: &crate::dialogue::post::Post, + dialogue: &crate::dialogue::Dialogue, + hilite: bool, + anchor_y: i16) -> TextView { + // create a textview with all of our default properties + let mut bubble_tv = default_textview(post, hilite, vp); + + // set alignment of bubble left/right + let mut align_right = false; + let mut anchor_x = vp.margin.x; // default to align left + if let Some(author) = dialogue.author(post.author_id()) { + if author.flag_is(AuthorFlag::Right) { + // align right + align_right = true; + anchor_x = vp.layout_screensize.x - vp.margin.x; + } + } + // set the text bounds of the bubble and the growth direction + let anchor = Point::new(anchor_x, anchor_y); + let width = vp.bubble_width; + let text_bounds = match (topdown, align_right) { + (true, true) => TextBounds::GrowableFromTr(anchor, width), + (true, false) => TextBounds::GrowableFromTl(anchor, width), + (false, true) => TextBounds::GrowableFromBr(anchor, width), + (false, false) => TextBounds::GrowableFromBl(anchor, width), + }; + + bubble_tv.bounds_hint = text_bounds; + bubble_tv +} + + pub struct Chat { cid: CID, } @@ -40,6 +127,18 @@ impl Chat { let chat_sid = xous::create_server().unwrap(); let chat_cid = xous::connect(chat_sid).unwrap(); + let busy_bumper = xous::create_server().unwrap(); + let busy_bumper_cid = xous::connect(busy_bumper).unwrap(); + + log::info!("starting idle animation runner"); + let run_busy_animation = Arc::new(AtomicBool::new(false)); + thread::spawn({ + let run_busy_animation = run_busy_animation.clone(); + move || { + busy_animator(busy_bumper, busy_bumper_cid, chat_cid, run_busy_animation); + } + }); + log::info!("Starting chat UI server",); thread::spawn({ move || { @@ -51,6 +150,8 @@ impl Chat { opcode_post, opcode_event, opcode_rawkeys, + run_busy_animation, + busy_bumper_cid, ); } }); @@ -222,6 +323,91 @@ impl Chat { .map(|_| ()) .expect("failed to Redraw Chat UI"); } + + /// Set the status bar text. + /// + /// # Arguments + /// + /// `msg` - the text to show + /// + /// This method implements the latest recommendation of panicing on internal errors. + pub fn set_status_text(&self, msg: &str) { + let bm = BusyMessage { + busy_msg: xous_ipc::String::from_str(msg) + }; + Buffer::into_buf(bm).expect("internal error").send( + self.cid, ChatOp::SetStatusText as u32 + ).expect("internal error"); + } + + pub fn set_busy_state(&self, run: bool) { + xous::send_message( + self.cid, + xous::Message::new_scalar(ChatOp::SetBusyAnimationState as usize, + if run { 1 } else { 0 }, + 0, 0, 0), + ) + .map(|_| ()) + .expect("internal error"); + } + + /// Set status bar text when system is idle. + /// This is a convenience so we don't have to track the ins/outs of busy/idle state + /// and update the text, especially when we have multiple potential servers vying + /// to set a busy state. + pub fn set_status_idle_text(&self, msg: &str) { + let bm = BusyMessage { + busy_msg: xous_ipc::String::from_str(msg) + }; + Buffer::into_buf(bm).expect("internal error").send( + self.cid, ChatOp::SetStatusIdleText as u32 + ).expect("internal error"); + } + +} + +/// Helper server that pumps the busy animation state until instructed to stop. +/// +/// # Arguments +/// +/// * `busy_bumper` - the server ID to use for the helper server +/// * `busy_bumper_cid` - the corresponding connection ID +/// * `chat_cid` - the CID to the main chat loop, used to initiate redraw events as necessary +/// * `run_busy_animation` - a shared `AtomicBool` which, when `true`, causes the loop to reschedule itself to run. +pub fn busy_animator( + busy_bumper: SID, + busy_bumper_cid : CID, + chat_cid: CID, + run_busy_animation: Arc) { + let tt = ticktimer_server::Ticktimer::new().unwrap(); + loop { + let msg = xous::receive_message(busy_bumper).unwrap(); + match FromPrimitive::from_usize(msg.body.id()) { + Some(BusyAnimOp::Start) => { + tt.sleep_ms(crate::BUSY_ANIMATION_RATE_MS).unwrap(); + xous::try_send_message(busy_bumper_cid, + xous::Message::new_scalar( + BusyAnimOp::Pump as usize, 0, 0, 0, 0) + ).ok(); + } + Some(BusyAnimOp::Pump) => { + if run_busy_animation.load(Ordering::SeqCst) { + xous::try_send_message(chat_cid, + xous::Message::new_scalar( + ChatOp::UpdateBusy as usize, 0, 0, 0, 0) + ).ok(); + tt.sleep_ms(crate::BUSY_ANIMATION_RATE_MS).unwrap(); + xous::try_send_message(busy_bumper_cid, + xous::Message::new_scalar( + BusyAnimOp::Pump as usize, 0, 0, 0, 0) + ).ok(); + } + } + _ => { + log::warn!("Unexpected message: {:?}", msg); + } + } + } } /// The Chat UI server a manages a Chat UI to read a display and navigate a @@ -245,18 +431,55 @@ pub fn server( opcode_post: Option, opcode_event: Option, opcode_rawkeys: Option, + run_busy_animation: Arc, + busy_bumper_cid: CID, ) -> ! { //log_server::init_wait().unwrap(); log::set_max_level(log::LevelFilter::Info); log::info!("my PID is {}", xous::process::id()); let mut ui = ui::Ui::new(sid, app_name, app_menu, app_cid, opcode_event); - + let mut dialogue_key = None; let mut allow_redraw = false; loop { let msg = xous::receive_message(sid).unwrap(); log::debug!("got message {:?}", msg); match FromPrimitive::from_usize(msg.body.id()) { + Some(ChatOp::UpdateBusy) => { + ui.redraw_busy().expect("CHAT couldn't redraw"); + } + Some(ChatOp::UpdateBusyForced) => { + ui.redraw_status_forced().expect("CHAT couldn't redraw"); + } + Some(ChatOp::SetStatusText) => { + let buffer = unsafe { + Buffer::from_memory_message(msg.body.memory_message().unwrap()) + }; + let s = buffer.to_original::().unwrap(); + ui.set_status_text(s.busy_msg.as_str().unwrap()); + } + Some(ChatOp::SetStatusIdleText) => { + let buffer = unsafe { + Buffer::from_memory_message(msg.body.memory_message().unwrap()) + }; + let s = buffer.to_original::().unwrap(); + ui.set_status_idle_text(s.busy_msg.as_str().unwrap()); + } + Some(ChatOp::SetBusyAnimationState) => msg_scalar_unpack!(msg, state, _, _, _, { + if state != 0 { + ui.set_busy_state(true); + if !run_busy_animation.swap(true, Ordering::SeqCst) { + // only send off the Pump request on the transition from false->true; this causes the machine to run + xous::try_send_message(busy_bumper_cid, + xous::Message::new_scalar( + BusyAnimOp::Pump as usize, 0, 0, 0, 0) + ).ok(); + } + } else { + run_busy_animation.store(false, Ordering::SeqCst); + ui.set_busy_state(false); + } + }), Some(ChatOp::DialogueSave) => { log::info!("ChatOp::DialogueSave"); ui.dialogue_save().expect("failed to save Dialogue"); @@ -270,7 +493,7 @@ pub fn server( let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; let dialogue = buffer.to_original::().unwrap(); - let dialogue_key = match dialogue.key { + dialogue_key = match dialogue.key { Some(key) => Some(key.to_string()), None => None, }; @@ -388,19 +611,24 @@ pub fn server( } Some(ChatOp::PostAdd) => { log::info!("ChatOp::PostAdd"); - let buffer = - unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; - match buffer.to_original::() { - Ok(post) => ui - .post_add( - post.dialogue_id.as_str().unwrap(), - post.author.as_str().unwrap(), - post.timestamp, - post.text.as_str().unwrap(), - None, // TODO implement - ) - .unwrap(), - Err(e) => log::warn!("failed to deserialize Post: {:?}", e), + match dialogue_key { + Some(ref dialogue_id) => { + let buffer = + unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; + match buffer.to_original::() { + Ok(post) => ui + .post_add( + &dialogue_id, + post.author.as_str().unwrap(), + post.timestamp, + post.text.as_str().unwrap(), + None, // TODO implement + ) + .unwrap(), + Err(e) => log::warn!("failed to deserialize Post: {:?}", e), + } + } + None => log::warn!("failed to PostAdd with Dialogue == None"), } } Some(ChatOp::PostDel) => { @@ -456,3 +684,25 @@ pub fn now() -> u64 { .try_into() .unwrap() } + +/// "context-free" (cf) communication with the chat object. +/// This is accomplished by making a copy of the connection to the chat server. +pub fn cf_set_status_text(chat_cid: xous::CID, msg: &str) { + let bm = BusyMessage { + busy_msg: xous_ipc::String::from_str(msg) + }; + Buffer::into_buf(bm).expect("internal error").send( + chat_cid, ChatOp::SetStatusText as u32 + ).expect("internal error"); +} + +pub fn cf_set_busy_state(chat_cid: xous::CID, run: bool) { + xous::send_message( + chat_cid, + xous::Message::new_scalar(ChatOp::SetBusyAnimationState as usize, + if run { 1 } else { 0 }, + 0, 0, 0), + ) + .map(|_| ()) + .expect("internal error"); +} \ No newline at end of file diff --git a/libs/chat/src/main.rs b/libs/chat/src/main.rs index a3ed51114..a5b4838b2 100644 --- a/libs/chat/src/main.rs +++ b/libs/chat/src/main.rs @@ -3,9 +3,12 @@ mod api; use api::*; +use num_traits::FromPrimitive; use std::thread; use xous::{Error, CID, SID}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; pub fn main() -> ! { let xns = xous_names::XousNames::new().unwrap(); @@ -17,10 +20,21 @@ pub fn main() -> ! { let chat_sid = xous::create_server().unwrap(); let chat_cid = xous::connect(chat_sid).unwrap(); + let busy_bumper = xous::create_server().unwrap(); + let busy_bumper_cid = xous::connect(busy_bumper).unwrap(); + + log::info!("starting idle animation runner"); + let run_busy_animation = Arc::new(AtomicBool::new(false)); + thread::spawn({ + let run_busy_animation = run_busy_animation.clone(); + move || { + busy_animator(busy_bumper, busy_bumper_cid, chat_cid, run_busy_animation); + } + }); log::info!("Starting chat server",); thread::spawn({ move || { - server(chat_sid, None, None); + server(chat_sid, None, None, run_busy_animation, busy_bumper_cid); } }); xous::terminate_process(0) diff --git a/libs/chat/src/ui.rs b/libs/chat/src/ui.rs index 869936265..a464d8ab7 100644 --- a/libs/chat/src/ui.rs +++ b/libs/chat/src/ui.rs @@ -4,21 +4,38 @@ use crate::icontray::Icontray; use dialogue::{post::Post, Dialogue}; use gam::{menu_matic, MenuMatic, UxRegistration}; use graphics_server::api::GlyphStyle; -use graphics_server::{DrawStyle, Gid, PixelColor, Point, Rectangle, TextBounds, TextView}; +use graphics_server::{DrawStyle, Gid, PixelColor, Point, Rectangle, TextBounds, TextView, Line}; use locales::t; use modals::Modals; use rkyv::de::deserializers::AllocDeserializer; use rkyv::ser::serializers::WriteSerializer; use rkyv::ser::Serializer; use rkyv::Deserialize; +use ticktimer_server::Ticktimer; use std::cmp::min; use std::convert::TryFrom; +use std::fmt::Write as TextWrite; use std::io::{Error, ErrorKind, Read, Write}; use xous::{MessageEnvelope, CID}; use xous_names::XousNames; +pub const BUSY_ANIMATION_RATE_MS: usize = 200; + +/// Variables that define the visual properties of the layout +pub struct VisualProperties { + pub canvas: Gid, + pub total_screensize: Point, + pub layout_screensize: Point, + /// height of the status bar. This is subtracted from screensize. + pub status_height: u16, + pub bubble_width: u16, + pub margin: Point, // margin to edge of canvas + pub bubble_margin: Point, // margin of text in bubbles + pub bubble_radius: u16, + pub bubble_space: i16, // spacing between text bubbles +} #[allow(dead_code)] pub(crate) struct Ui { // optional structures that indicate new input to the Chat loop per iteration @@ -34,30 +51,36 @@ pub(crate) struct Ui { dialogue: Option, // Callbacks: + // callback to our own server + self_cid: CID, // optional SID of the "Owner" Chat App to receive UI-events app_cid: Option, // optional opcode ID to process UI-event msgs opcode_event: Option, - canvas: Gid, gam: gam::Gam, modals: Modals, + tt: Ticktimer, - // variables regarding the posts currently onscreen - // the selected post is hilighted onscreen and the focus of the msg menu F4 - post_selected: Option, - // the anchor post is drawn first, at the top or bottom of the screen - post_anchor: Option, - // layout post bubbles on the screen from top-down or bottom-up - post_topdown: bool, + /// These variables are managed exclusively by the layout routine. + /// the selected post is highlighted onscreen and the focus of the msg menu + layout_selected: Option, + /// the range of posts that are currently drawable. This was originally implemented as + /// an Option, but we need to be able to do RangeInclusive and Reversed ranges. + /// The RangeBounds trait isn't object-safe, so we can't Box/dyn it either... + /// So, instead, we turn the ranges into a Vec and operate from there... + layout_range: Vec, + /// layout post bubbles on the screen from top-down or bottom-up + layout_topdown: bool, - // variables that define our graphical attributes - screensize: Point, - bubble_width: u16, - margin: Point, // margin to edge of canvas - bubble_margin: Point, // margin of text in bubbles - bubble_radius: u16, - bubble_space: i16, // spacing between text bubbles + /// TextView for the status bar. This encapsulates the state of the busy animation, and the text within. + status_tv: TextView, + /// Track the last time we update the status bar; use this avoid double-updating busy animations + status_last_update_ms: u64, + /// The default message to show when we exit a busy state + status_idle_text: String, + + vp: VisualProperties, // variables that define a menu menu_mode: bool, @@ -114,6 +137,36 @@ impl Ui { .expect("couldn't create MenuMatic manager"); let pddb = pddb::Pddb::new(); pddb.try_mount(); + + // setup the initial status bar contents + let margin = Point::new(4, 4); + let status_height = gam.glyph_height_hint( + GlyphStyle::Regular).unwrap() as u16; + let mut status_tv = TextView::new(canvas, + TextBounds::BoundingBox(Rectangle::new( + Point::new(0, 0), + Point::new(screensize.x, status_height as _), + )) + ); + status_tv.style = GlyphStyle::Regular; + status_tv.margin = margin; + status_tv.draw_border = false; + status_tv.clear_area = true; + status_tv.margin = Point::new(0, 0); + write!(status_tv, "{}", t!("chat.status.initial", locales::LANG).to_string()).ok(); + let tt = ticktimer_server::Ticktimer::new().unwrap(); + let status_last_update_ms = tt.elapsed_ms(); + let bubble_properties = VisualProperties { + canvas, + total_screensize: screensize, + layout_screensize: Point::new(screensize.x, screensize.y - status_height as i16), + status_height, + bubble_width: ((screensize.x / 5) * 4) as u16, // 80% width for the text bubbles + margin: Point::new(4, 4), + bubble_margin: Point::new(4, 4), + bubble_radius: 4, + bubble_space: 4, + }; Ui { input: None, msg: None, @@ -121,24 +174,23 @@ impl Ui { pddb_dict: None, pddb_key: None, dialogue: None, + self_cid: xous::connect(sid).unwrap(), app_cid, opcode_event, - canvas, gam, modals, - screensize, - post_selected: None, - post_anchor: None, - post_topdown: false, - bubble_width: ((screensize.x / 5) * 4) as u16, // 80% width for the text bubbles - margin: Point::new(4, 4), - bubble_margin: Point::new(4, 4), - bubble_radius: 4, - bubble_space: 4, + tt, + status_tv, + status_last_update_ms, + layout_selected: None, + layout_range: Vec::new(), + layout_topdown: false, + vp: bubble_properties, menu_mode: true, app_menu: app_menu.to_owned(), menu_mgr: menu_mgr, token: token, + status_idle_text: t!("chat.status.initial", locales::LANG).to_string(), } } @@ -165,9 +217,9 @@ impl Ui { { Ok(dialogue) => { // show most recent posts onscreen - self.post_selected = dialogue.post_last(); - self.post_anchor = self.post_selected; - self.post_topdown = false; + self.layout_selected = dialogue.post_last(); + self.layout_range.clear(); + self.layout_topdown = false; Some(dialogue) } Err(e) => { @@ -351,12 +403,12 @@ impl Ui { ) -> Result<(), Error> { match (&self.pddb_key, &mut self.dialogue) { (Some(pddb_key), Some(ref mut dialogue)) => { - if pddb_key.eq(&dialogue_id) { + if dialogue_id.len() == 0 || pddb_key.eq(&dialogue_id) { dialogue - .post_add(author, timestamp, text, attach_url) + .post_add(author, timestamp, text, attach_url, Some((&self.vp, &self.gam))) .unwrap(); } else { - log::warn!("dropping Post as dialogue_id does not match pddb_key"); + log::warn!("dropping Post as dialogue_id does not match pddb_key: '{}' vs '{}'", pddb_key, dialogue_id); } } (None, _) => log::warn!("no pddb_key set to match dialogue_id"), @@ -417,11 +469,11 @@ impl Ui { /// /// * `index` - POST_SELECT_NEXT or POST_SELECT_PREV or an arbitraty index pub fn post_select(&mut self, index: usize) { - self.post_selected = match &self.dialogue { + self.layout_selected = match &self.dialogue { Some(dialogue) => { match dialogue.post_last() { Some(last_post) => { - match (index, self.post_selected) { + match (index, self.layout_selected) { (POST_SELECTED_NEXT, Some(selected)) => { if selected >= last_post { self.event(Event::Bottom); @@ -478,65 +530,15 @@ impl Ui { } } - /// Return a TextView bubble representing a Dialogue Post + /// Clear the screen area, not including the status bar /// - /// # Arguments - /// - /// * `post` - the post to represent in a TextView bubble - /// * `dialogue` - containing the Post for context info - /// * `hilite` - hilite this Post on the screen (thicker border) - /// * `anchor_y` - the vertical position on screen to draw TextView bubble - /// - fn bubble(&self, post: &Post, dialogue: &Dialogue, hilite: bool, anchor_y: i16) -> TextView { - // set alignment of bubble left/right - let mut align_right = false; - let mut anchor_x = self.margin.x; // default to align left - if let Some(author) = dialogue.author(post.author_id()) { - if author.flag_is(AuthorFlag::Right) { - // align right - align_right = true; - anchor_x = self.screensize.x - self.margin.x; - } - } - - // set the text bounds of the bubble and the growth direction - let anchor = Point::new(anchor_x, anchor_y); - let width = self.bubble_width; - let text_bounds = match (self.post_topdown, align_right) { - (true, true) => TextBounds::GrowableFromTr(anchor, width), - (true, false) => TextBounds::GrowableFromTl(anchor, width), - (false, true) => TextBounds::GrowableFromBr(anchor, width), - (false, false) => TextBounds::GrowableFromBl(anchor, width), - }; - - // create the bubble with the anchor and a growable direction - use std::fmt::Write; - let mut bubble_tv = TextView::new(self.canvas, text_bounds); - if hilite { - bubble_tv.border_width = 3; - } else { - bubble_tv.border_width = 1; - } - bubble_tv.draw_border = true; - bubble_tv.clear_area = true; - bubble_tv.rounded_border = Some(self.bubble_radius); - bubble_tv.style = GlyphStyle::Regular; - bubble_tv.margin = self.bubble_margin; - bubble_tv.ellipsis = false; - bubble_tv.insertion = None; - write!(bubble_tv.text, "{}", post.text()).expect("couldn't write history text to TextView"); - bubble_tv - } - - // Clear the screen area - // fn clear_area(&self) { self.gam .draw_rectangle( - self.canvas, + self.vp.canvas, Rectangle::new_with_style( - Point::new(0, 0), - self.screensize, + Point::new(0, self.vp.status_height as i16), + self.vp.total_screensize, DrawStyle { fill_color: Some(PixelColor::Light), stroke_color: None, @@ -570,10 +572,8 @@ impl Ui { /// pub(crate) fn redraw(&mut self) -> Result<(), xous::Error> { if self.dialogue.is_some() { - let mut attempt = 0; - while !self.layout().unwrap_or(true) && attempt < 3 { - attempt += 1; - } + self.layout().expect("layout failed to execute"); + self.status_last_update_ms = self.tt.elapsed_ms(); } else { self.clear_area(); // no dialogue so clear screen } @@ -582,115 +582,263 @@ impl Ui { Ok(()) } + /// Update the busy state. Does not touch any other aspect of the screen layout. + pub(crate) fn redraw_busy(&mut self) -> Result<(), xous::Error> { + let curtime = self.tt.elapsed_ms(); + if curtime - self.status_last_update_ms > BUSY_ANIMATION_RATE_MS as u64 { + self.gam.post_textview(&mut self.status_tv)?; + self.gam.redraw().expect("couldn't redraw screen"); + self.status_last_update_ms = curtime; + } + Ok(()) + } + + /// Update the status bar, without any throttling + pub(crate) fn redraw_status_forced(&mut self) -> Result<(), xous::Error> { + self.gam.post_textview(&mut self.status_tv)?; + self.gam.redraw().expect("couldn't redraw screen"); + let curtime = self.tt.elapsed_ms(); + self.status_last_update_ms = curtime; + Ok(()) + } + + /// Returns `true` if the status bar is currently set for the busy animation + pub(crate) fn is_busy(&self) -> bool { + self.status_tv.busy_animation_state.is_some() + } + + /// Set the status bar text + pub(crate) fn set_status_text(&mut self, msg: &str) { + self.status_tv.clear_str(); + write!(self.status_tv, "{}", msg).ok(); + xous::send_message(self.self_cid, + xous::Message::new_scalar( + ChatOp::UpdateBusy as usize, 0, 0, 0, 0) + ).ok(); + } + /// Sets the status bar to animate the busy animation + pub(crate) fn set_busy_state(&mut self, run: bool) { + if run { + self.status_tv.busy_animation_state = Some(0); // the "glitch" to 0 is intentional, gives an indicator that a new op has started + } else { + if self.status_tv.busy_animation_state.take().is_some() { + self.status_tv.clear_str(); + write!(self.status_tv, "{}", self.status_idle_text).ok(); + // force the update, to ensure the idle state text is actually rendered + xous::send_message(self.self_cid, + xous::Message::new_scalar( + ChatOp::UpdateBusyForced as usize, 0, 0, 0, 0) + ).ok(); + } + } + } + /// Set the default idle text. Does *not* cause a redraw. If you need + /// an instant re-draw, call `set_status_text()` + pub(crate) fn set_status_idle_text(&mut self, msg: &str) { + self.status_idle_text = msg.to_owned(); + } + /// Layout the post bubbles on the screen. /// - /// The layout proceeds from top-down or bottom-up (starting with the - /// `post_anchor`), drawing a bubble for each Post, until the available space - /// is exhausted. - /// * If the `post_selected` is fully displayed, then `Ok(true)` is Returned. - /// * If the `post_selected` is NOT fully displayed, then the `post_anchor` - /// is set as the `post_anchor`, and `Ok(false)` is Returned - signalling that - /// a re-layout is in order. - /// * If the first/last Post is fully displayed, then the `post_topdown` is - /// toggled, the `post_anchor` is set to the first/last Post, and `Ok(false)` - /// is Returned - signalling that a re-layout is in order. + /// The challenge is to layout a sub-set of the posts on screen, ensuring that + /// the selected-post is fully displayed, and to do something non-jarring as the + /// user moves the selection up or down. /// - /// Error if there if Dialogue is None - /// - fn layout(&mut self) -> Result { - self.clear_area(); - match (&self.dialogue, &self.post_anchor) { - (Some(dialogue), Some(post_anchor)) => { - log::info!("redrawing dialogue: {}", dialogue.title); - let mut post_selected_visible = false; - let mut bubble_count = 0; + /// That is, when the user clicks up then the currently selected post should go + /// un-bold, and the post above should go bold, without movement - unless the newly + /// selected post is partially or fully off-screen, in which case, the posts need + /// to move down. There are three edge cases, when the first or last post is reached, + /// or when the post is too big for the screen. And an additional challenge, + /// that the only way to calculate the vertical height of a post is to lay it out. + fn layout(&mut self) -> Result<(), Error> { + if let Some(dialogue) = self.dialogue.as_mut() { + log::info!("redrawing dialogue: {}", dialogue.title); - // initialise the first post index AND the vertical position on the screen - let mut post_index = *post_anchor; - let mut anchor_y = match self.post_topdown { - true => 0, - false => self.screensize.y - self.margin.y, + // 1. Consistency check the layout range versus selected post. + let search_required = if let Some(selected) = self.layout_selected { + !self.layout_range.contains(&selected) + } else { + true + }; + + // 2. Adjust the displayable range. + if search_required { + let starting_at = if let Some(selected) = self.layout_selected { + if self.layout_range.len() > 0 { + self.layout_topdown = selected <= *self.layout_range.iter().min().unwrap_or(&0); + selected + } else { + // if no range is available, go from the bottom up, starting with the selected post + self.layout_topdown = false; + selected + } + } else { + // no post selected, always layout from bottom up, starting at the most recent post + self.layout_topdown = false; + dialogue.post_last().unwrap_or(0) }; - - // fill the screen with post bubbles from top-down or bottom-up - let mut post_is_fully_visible = true; - let mut is_selected = false; - while post_is_fully_visible { - log::trace!("redrawing post: {post_index}"); - match (dialogue.post_get(post_index), &self.post_selected) { - (Some(post), Some(post_selected)) => { - is_selected = post_index == *post_selected; - - // create a bubble and place on canvas - let mut bubble_tv = self.bubble(post, dialogue, is_selected, anchor_y); - self.gam - .post_textview(&mut bubble_tv) - .expect("couldn't render bubble textview"); - bubble_count += 1; - post_is_fully_visible = !bubble_tv.overflow.unwrap_or(true); - if post_is_fully_visible { - // step to the next post AND the next vertical position on the screen - match bubble_tv.bounds_computed { - Some(bounds) => match self.post_topdown { - true => { - if post_index - >= dialogue.post_last().unwrap_or(usize::MAX) - { - log::info!("trigger a re-layout from bottom-up"); - self.post_topdown = false; - self.post_anchor = dialogue.post_last(); - return Ok(false); - } - post_index += 1; - anchor_y += (bounds.br.y - bounds.tl.y) - + self.bubble_space - + self.bubble_margin.y; - } - false => { - if post_index == 0 { - log::info!("trigger a re-layout from top-down"); - self.post_topdown = true; - self.post_anchor = Some(0); - return Ok(false); - } - post_index -= 1; - anchor_y -= (bounds.br.y - bounds.tl.y) - + self.bubble_space - + self.bubble_margin.y; - } - }, - None => { - log::info!("bubble is offscreen so noop"); - post_is_fully_visible = false; - } - }; - - // check if the selected post is fully visible - post_selected_visible = post_selected_visible || is_selected; + let mut fwd_iter; + let mut rev_iter; + let search_window: &mut dyn Iterator = if self.layout_topdown { + // search from the selected post to all newer posts, top-to-down + fwd_iter = dialogue.posts_as_slice_mut()[starting_at..].iter_mut(); + &mut fwd_iter + } else { + // search from oldest post to selected post, bottom-to-top + if dialogue.posts_as_slice().len() > 0 { + rev_iter = dialogue.posts_as_slice_mut()[..=starting_at].iter_mut().rev(); + } else { + // zero-length case we still have to return an empty iterator, but + // we can't have the range be inclusive and the code still work + rev_iter = dialogue.posts_as_slice_mut().iter_mut().rev(); + } + &mut rev_iter + }; + let mut total_height = 0; + self.layout_range.clear(); + for (i, post) in search_window.enumerate() { + let next_height = if let Some(bb) = post.bounding_box { + bb.height() + self.vp.bubble_space as u32 + self.vp.bubble_margin.y as u32 + } else { + // if the "natural height" has not been computed, do so now. + let mut layout_bubble = default_textview(post, false, &self.vp); + log::debug!("compute bounds on {}", layout_bubble); + if self.gam.bounds_compute_textview(&mut layout_bubble).is_ok() { + post.bounding_box = layout_bubble.bounds_computed; + match layout_bubble.bounds_computed { + Some(r) => r.height() + self.vp.bubble_space as u32 + self.vp.bubble_margin.y as u32, + None => { + log::warn!("Unexpected null bounds in computing textview heights, layout will be incorrect."); + 0 + } } + } else { + log::warn!("Unexpected error in computing textview heights, layout will be incorrect."); + 0 } - (None, _) => { - log::trace!("not enough post bubbles to fill the screen"); - return Ok(true); + }; + if total_height + next_height > self.vp.layout_screensize.y as u32 { + if self.layout_topdown { + self.layout_range = (starting_at..starting_at + i).collect(); + } else { + self.layout_range = (starting_at - i..=starting_at).rev().collect(); + } + break + } + total_height += next_height; + } + if self.layout_range.len() == 0 { + // not enough elements to fill the entire screen. Just select everything from selected + // to the last possible message. + log::debug!("Not enough elements to fill the screen"); + if self.layout_topdown { + self.layout_range = (starting_at..).collect(); + } else { + if dialogue.posts_as_slice().len() > 0 { + self.layout_range = (0..=starting_at).rev().collect(); + } else { + // "empty range" in case of no posts + self.layout_range = (0..0).rev().collect(); } - (_, _) => return Ok(true), // get me outa-here } } - if post_selected_visible || (bubble_count == 1 && is_selected) { - Ok(true) + } + assert!(dialogue.posts_as_slice().len() == 0 || self.layout_range.len() > 0, "Layout range should be set at this point."); + + // 3. clear the entire area, and re-draw the status bar + self.gam + .draw_rectangle( + self.vp.canvas, + Rectangle::new_with_style( + Point::new(0, 0), + self.vp.total_screensize, + DrawStyle { + fill_color: Some(PixelColor::Light), + stroke_color: None, + stroke_width: 0, + }, + ), + ) + .expect("can't clear canvas area"); + + // 4. draw the text bubbles, in the order computed in step 2. + let mut y = if self.layout_topdown { + self.vp.status_height as i16 + self.vp.bubble_margin.y + } else { + self.vp.status_height as i16 + self.vp.layout_screensize.y - self.vp.bubble_margin.y + }; + log::debug!("Laying out with selected {:?} in range {:?}; topdown: {:?}", self.layout_selected, self.layout_range, self.layout_topdown); + for &post_index in &self.layout_range { + let post = match dialogue.post_get(post_index) { + Some(p) => p, + None => { + log::warn!("Expected post at index {}, returned nothing. Range {:?}, posts {:?}", + post_index, self.layout_range, dialogue.posts_as_slice() + ); + continue; + } + }; + let highlight = if let Some(selected) = self.layout_selected { + selected == post_index } else { - log::info!("trigger a re-layout with selected post visible"); - self.post_topdown = self.post_selected >= self.post_anchor; - self.post_anchor = self.post_selected; - Ok(false) + false + }; + let mut bubble_tv = bubble( + &self.vp, self.layout_topdown, post, dialogue, highlight, y); + self.gam + .post_textview(&mut bubble_tv) + .expect("couldn't render bubble textview"); + // double check the actual bounds against expected bounds + match bubble_tv.bounds_computed { + Some(actual_r) => { + let expected_r = post.bounding_box.expect("bb should be computed by now"); + if expected_r.height() != actual_r.height() { + log::warn!( + "Height mismatch of drawn versus pre-computed text (expected {}, got {}) for {}", + expected_r.height(), + actual_r.height(), + bubble_tv.to_str() + ); + } + if self.layout_topdown { + y += actual_r.height() as i16; + } else { + y -= actual_r.height() as i16; + } + // sanity check the computations + if y > self.vp.layout_screensize.y + self.vp.status_height as i16 + || y < self.vp.status_height as i16 { + log::error!("Computed range of elements sent to layout overflows at index {}", post_index); + // stop laying out to avoid text artifacts + break; + } + // add y-margin before the next iteration + if self.layout_topdown { + y += self.vp.bubble_space + self.vp.bubble_margin.y; + } else { + y -= self.vp.bubble_space + self.vp.bubble_margin.y; + } + } + _ => { + log::error!("No bounds computed for {}, this is a GAM or typesetter bug!", bubble_tv.to_str()); + } } } - (Some(_dialogue), None) => { - log::info!("no posts to display"); - // TODO show dialogue info as a default? - Ok(true) - } - (None, _) => Err(Error::new(ErrorKind::InvalidData, "missing dialogue")), + + // 5. draw status bar on top of any post that happens to flow over the top... + self.gam.post_textview(&mut self.status_tv) + .expect("couldn't render status bar"); + let status_border = Line::new( + Point::new(0, self.vp.status_height as i16), + Point::new(self.vp.total_screensize.x, self.vp.status_height as i16) + ); + self.gam.draw_line(self.vp.canvas, + status_border + ).expect("couldn't draw status lower border"); + + Ok(()) + } else { + Err(Error::new(ErrorKind::InvalidData, "missing dialogue")) } } } diff --git a/libs/mass-storage/usbd_bulk_only_transport/Cargo.toml b/libs/mass-storage/usbd_bulk_only_transport/Cargo.toml index f2fd0813c..8c07a89d5 100644 --- a/libs/mass-storage/usbd_bulk_only_transport/Cargo.toml +++ b/libs/mass-storage/usbd_bulk_only_transport/Cargo.toml @@ -20,7 +20,7 @@ typenum = "1.11.2" usbd_mass_storage = { version = "0.1.0", path = "../usbd_mass_storage" } packing = { version = "0.2.0", path = "../packing/packing" } log = "0.4.17" -xous = "0.9.53" -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } +xous = "0.9.55" +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } [features] diff --git a/libs/mass-storage/usbd_scsi/Cargo.toml b/libs/mass-storage/usbd_scsi/Cargo.toml index 27e8e103b..663d12871 100644 --- a/libs/mass-storage/usbd_scsi/Cargo.toml +++ b/libs/mass-storage/usbd_scsi/Cargo.toml @@ -22,7 +22,7 @@ usbd_mass_storage = { version = "0.1.0", path = "../usbd_mass_storage" } packing = { version = "0.2.0", path = "../packing/packing" } usbd_bulk_only_transport = { version = "0.1.0", path = "../usbd_bulk_only_transport" } log = "0.4.17" -xous = "0.9.53" -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } +xous = "0.9.55" +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } [features] diff --git a/libs/tls/Cargo.toml b/libs/tls/Cargo.toml index 731d20bf3..ea1475967 100644 --- a/libs/tls/Cargo.toml +++ b/libs/tls/Cargo.toml @@ -9,11 +9,11 @@ version = "0.1.0" log = "0.4.14" num-derive = { version = "0.3.3", default-features = false} num-traits = { version = "0.2.14", default-features = false} -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } modals = { path = "../../services/modals" } net = { path = "../../services/net" } diff --git a/libs/tls/README.md b/libs/tls/README.md index a0dda3812..634657a09 100644 --- a/libs/tls/README.md +++ b/libs/tls/README.md @@ -15,8 +15,8 @@ Note that hosts with self-signed Certificates may be probed and trusted (e.g. [c A small set of shellchat commands are included to enact the base functionality: - `net tls probe ` will initiate a modified tls handshake with ``, obtain the certificate chain offered by ``, and immediately terminate the connection. A call to Tls::check_trust() will present the CA certificate chain in a modal to be individually selected and saved to PDDB if trusted. -- `net tls test ` will attempt a normal tls handshake with `` based on the trusted Root CA certificates in the PDDB. If the connection is successful, then a simple `get` is emitted, the response accepted, and the connection closed. -- `net tls mozilla` trusts and saves all Root CA's in the [webpki-roots crate](https://crates.io/crates/webpki-roots) - which contains Mozilla's root certificates. (requires `--feature rootCA`) +- `net tls test ` will attempt a normal tls handshake with `` based on the trusted Root CA certificates in the PDDB. If the connection is successful, then a simple `get` is emitted, the response accepted, and the connection closed. +- `net tls mozilla` trusts and saves all Root CA's in the [webpki-roots crate](https://crates.io/crates/webpki-roots) - which contains Mozilla's root certificates. (requires `--feature rootCA`) - `net list` lists all trusted certificates in the PDDB - `net deleteall` deletes all trusted certificates in the PDDB @@ -37,5 +37,5 @@ Considerations: - there is an outstanding **issue** where the `checkbox` modal does not display options with multiple lines correctly. This impacts the display of fingerprints for some certificates with longer subjects. - It may be worth considering rendering public `pddb::KEY_NAME_LEN` to allow developer to avoid sending overly long key's which will be rejected by the pddb. - probably need a shellchat `net tls delete ` -- some broken code is included for a future fix if required [RustlsOwnedTrustAnchor::publik_key()](https://github.com/betrusted-io/xous-core/pull/394/commits/4e0298c17ad2c51aa220a88dc69bf2f56e51076f) +- some broken code is included for a future fix if required [RustlsOwnedTrustAnchor::public_key()](https://github.com/betrusted-io/xous-core/pull/394/commits/4e0298c17ad2c51aa220a88dc69bf2f56e51076f) - some hosts (eg bunnyfoo.com) emit an `unexpected eof` error during tls handshake 🤷 diff --git a/libs/tls/locales/i18n.json b/libs/tls/locales/i18n.json index f6f4cdea3..303ca9028 100644 --- a/libs/tls/locales/i18n.json +++ b/libs/tls/locales/i18n.json @@ -40,8 +40,20 @@ "en-tts": "save host CA'a if trusted" }, "tls.probe_fail_servername": { - "en": "failed to create sever name from", - "en-tts": "failed to create sever name from" + "en": "failed to create server name from", + "en-tts": "failed to create server name from" + }, + "tls.probe_invalid_certificate": { + "en": "Invalid certificate : ", + "en-tts": "Invalid certificate : " + }, + "tls.probe_help_unknown_issuer": { + "en": "you may choose to trust a known CA", + "en-tts": "you may choose to trust a known CA" + }, + "tls.probe_help_not_valid_yet": { + "en": "error maybe caused by an improperly set clock", + "en-tts": "error maybe caused by an improperly set clock" }, "tls.probe_done": { "en": "trusted CA's", diff --git a/libs/tls/src/cmd.rs b/libs/tls/src/cmd.rs index ebc533cbe..9df851149 100644 --- a/libs/tls/src/cmd.rs +++ b/libs/tls/src/cmd.rs @@ -111,8 +111,7 @@ pub fn shellchat<'a>( .unwrap(); log::info!("connect TCPstream to {}", target); - let url = format!("{}:443", target); - match TcpStream::connect(url) { + match TcpStream::connect((target, 443)) { Ok(mut sock) => { log::info!("tcp connected"); write!(ret, "{}", t!("tls.test_success_tcp", locales::LANG)).ok(); diff --git a/libs/tls/src/danger.rs b/libs/tls/src/danger.rs index b7c606788..f47acf11c 100644 --- a/libs/tls/src/danger.rs +++ b/libs/tls/src/danger.rs @@ -1,5 +1,8 @@ +use locales::t; +use modals::Modals; use rustls::client::{ServerCertVerified, WebPkiVerifier}; use rustls::{CertificateError, Error, RootCertStore}; +use xous_names::XousNames; pub struct StifledCertificateVerification { pub roots: RootCertStore, @@ -28,8 +31,30 @@ impl rustls::client::ServerCertVerifier for StifledCertificateVerification { now, ) { Ok(ok) => Ok(ok), - Err(Error::InvalidCertificate(CertificateError::UnknownIssuer)) => { - Ok(ServerCertVerified::assertion()) + Err(Error::InvalidCertificate(e)) => { + let xns = XousNames::new().unwrap(); + let modals = Modals::new(&xns).unwrap(); + modals + .show_notification( + format!("{}\n{:?}", t!("tls.probe_invalid_certificate", locales::LANG), e).as_str(), + None, + ) + .expect("modal failed"); + match e { + CertificateError::UnknownIssuer => { + modals + .show_notification(t!("tls.probe_help_unknown_issuer", locales::LANG), None) + .expect("modal failed"); + Ok(ServerCertVerified::assertion()) + } + CertificateError::NotValidYet => { + modals + .show_notification(t!("tls.probe_help_not_valid_yet", locales::LANG), None) + .expect("modal failed"); + Err(Error::InvalidCertificate(e)) + } + _ => Err(Error::InvalidCertificate(e)), + } } Err(e) => Err(e), } diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index c7c862221..ea6b549ca 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -278,7 +278,7 @@ impl Tls { root_store } - /// Probes the target and returns true is at least 1 certificate is trusted + /// Probes the target and returns a count of newly trusted 1 certificates /// /// Establishes a tls connection to the target host, extracts the /// certificates offered and immediately closes the connection. @@ -311,8 +311,7 @@ impl Tls { let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); log::info!("connect TCPstream to {}", target); - let url = format!("{}:443", target); - match TcpStream::connect(url) { + match TcpStream::connect((target, 443)) { Ok(mut sock) => { match conn.complete_io(&mut sock) { Ok(_) => log::info!("handshake complete"), diff --git a/libs/userprefs/Cargo.toml b/libs/userprefs/Cargo.toml index e9d6e00ae..12af6e907 100644 --- a/libs/userprefs/Cargo.toml +++ b/libs/userprefs/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -xous-names = {package = "xous-api-names", version = "0.9.51"} +xous-names = {package = "xous-api-names", version = "0.9.53"} pddb = {path = "../../services/pddb"} keyboard = {path = "../../services/keyboard"} bincode = {version = "2.0.0-rc.2"} diff --git a/libs/xous-pio/Cargo.toml b/libs/xous-pio/Cargo.toml index beeda279c..5be709191 100644 --- a/libs/xous-pio/Cargo.toml +++ b/libs/xous-pio/Cargo.toml @@ -13,7 +13,7 @@ defmt = {version ="0.3", optional = true} utralib = {version = "0.1.23", default-features = false } [target.'cfg(target_os = "xous")'.dependencies] -xous = "0.9.53" +xous = "0.9.55" [features] cramium-soc = ["utralib/cramium-soc"] diff --git a/libs/xous-pl230/Cargo.toml b/libs/xous-pl230/Cargo.toml index 6234c8c9d..9cee741ae 100644 --- a/libs/xous-pl230/Cargo.toml +++ b/libs/xous-pl230/Cargo.toml @@ -14,7 +14,7 @@ pio-proc = "0.2.2" pio = "0.2.1" [target.'cfg(target_os = "xous")'.dependencies] -xous = "0.9.53" +xous = "0.9.55" [features] cramium-soc = ["utralib/cramium-soc"] diff --git a/services/aes-test/Cargo.toml b/services/aes-test/Cargo.toml index e171b2293..028f24977 100644 --- a/services/aes-test/Cargo.toml +++ b/services/aes-test/Cargo.toml @@ -10,8 +10,8 @@ version = "0.1.0" aes = { path = "../aes" } hex-literal = "0.3.1" log = "0.4.14" -log-server = { package = "xous-api-log", version = "0.1.49" } -xous = "0.9.53" +log-server = { package = "xous-api-log", version = "0.1.51" } +xous = "0.9.55" [features] default = [] diff --git a/services/codec/Cargo.toml b/services/codec/Cargo.toml index 069a9e4a9..88a6b1fe3 100644 --- a/services/codec/Cargo.toml +++ b/services/codec/Cargo.toml @@ -7,16 +7,16 @@ description = "Audio CODEC server" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} llio = {path = "../llio"} trng = {path = "../trng"} -xous-ipc = "0.9.53" +xous-ipc = "0.9.55" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} diff --git a/services/com/Cargo.toml b/services/com/Cargo.toml index 0e39c0607..21e9966a0 100644 --- a/services/com/Cargo.toml +++ b/services/com/Cargo.toml @@ -9,13 +9,13 @@ version = "0.1.0" [dependencies] com_rs = { git = "https://github.com/betrusted-io/com_rs", rev = "891bdd3ca8e41f81510d112483e178aea3e3a921" } log = "0.4.14" -log-server = {package = "xous-api-log", version = "0.1.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} -susres = {package = "xous-api-susres", version = "0.9.49"} +log-server = {package = "xous-api-log", version = "0.1.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} +susres = {package = "xous-api-susres", version = "0.9.51"} typenum = "1.12" -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} trng = {path = "../trng"} llio = {path = "../llio"} diff --git a/services/content-plugin-api/Cargo.toml b/services/content-plugin-api/Cargo.toml index e90921fab..a272c1c88 100644 --- a/services/content-plugin-api/Cargo.toml +++ b/services/content-plugin-api/Cargo.toml @@ -7,9 +7,9 @@ description = "Content Canvas Plugin Common APIs" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" +xous = "0.9.55" rkyv = { version = "0.4.3", default_features = false } graphics-server = { path="../graphics-server"} # this is used by the IMEF portion of the API -xous-names = {package = "xous-api-names", version = "0.9.51"} # used by the IMEF for registering listeners +xous-names = {package = "xous-api-names", version = "0.9.53"} # used by the IMEF for registering listeners log = "0.4.14" diff --git a/services/cram-console/Cargo.toml b/services/cram-console/Cargo.toml index db7223f1d..e3b5a52c5 100644 --- a/services/cram-console/Cargo.toml +++ b/services/cram-console/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" [dependencies] utralib = {version = "0.1.23", optional = true, default-features = false } -xous-api-names = "0.9.51" -xous-api-ticktimer = "0.9.49" -xous = "0.9.53" -log-server = {package = "xous-api-log", version = "0.1.49"} +xous-api-names = "0.9.53" +xous-api-ticktimer = "0.9.51" +xous = "0.9.55" +log-server = {package = "xous-api-log", version = "0.1.51"} log = "0.4.14" xous-pio = {path = "../../libs/xous-pio", optional = true} xous-pl230 = {path = "../../libs/xous-pl230", optional = true} diff --git a/services/cram-mbox1/Cargo.toml b/services/cram-mbox1/Cargo.toml index c1587eb9b..fbbb435db 100644 --- a/services/cram-mbox1/Cargo.toml +++ b/services/cram-mbox1/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" [dependencies] utralib = {version = "0.1.23", optional = true, default-features = false } -xous-api-names = "0.9.51" -xous-api-ticktimer = "0.9.49" -xous = "0.9.53" -log-server = {package = "xous-api-log", version = "0.1.49"} +xous-api-names = "0.9.53" +xous-api-ticktimer = "0.9.51" +xous = "0.9.55" +log-server = {package = "xous-api-log", version = "0.1.51"} log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/services/cram-mbox2/Cargo.toml b/services/cram-mbox2/Cargo.toml index 879c79bcd..04c4b420f 100644 --- a/services/cram-mbox2/Cargo.toml +++ b/services/cram-mbox2/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" [dependencies] utralib = {version = "0.1.23", optional = true, default-features = false } -xous-api-names = "0.9.51" -xous-api-ticktimer = "0.9.49" -xous = "0.9.53" -log-server = {package = "xous-api-log", version = "0.1.49"} +xous-api-names = "0.9.53" +xous-api-ticktimer = "0.9.51" +xous = "0.9.55" +log-server = {package = "xous-api-log", version = "0.1.51"} log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/services/dns/Cargo.toml b/services/dns/Cargo.toml index a14cc3f5a..763689641 100644 --- a/services/dns/Cargo.toml +++ b/services/dns/Cargo.toml @@ -7,15 +7,15 @@ description = "Xous DNS resolver" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} net = {path = "../net"} -xous-ipc = "0.9.53" +xous-ipc = "0.9.55" rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} trng = {path = "../trng"} @@ -28,7 +28,7 @@ chrono = {version = "0.4.19", default-features = false, features = ["std"]} sntpc = {version = "0.3.1"} #, features = ["log"] locales = {path = "../../locales"} gam = {path = "../gam"} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} userprefs = {path = "../../libs/userprefs"} modals = {path = "../modals"} diff --git a/services/early_settings/Cargo.toml b/services/early_settings/Cargo.toml index fc15665e4..bea3a37aa 100644 --- a/services/early_settings/Cargo.toml +++ b/services/early_settings/Cargo.toml @@ -8,12 +8,12 @@ edition = "2021" [dependencies] log = {version = "0.4", features = ["max_level_trace", "release_max_level_trace"]} -log-server = {package = "xous-api-log", version = "0.1.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} -susres = {package = "xous-api-susres", version = "0.9.49"} +log-server = {package = "xous-api-log", version = "0.1.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} +susres = {package = "xous-api-susres", version = "0.9.51"} spinor = {path = "../../services/spinor"} num-derive = {version = "0.3.3", default-features = false} diff --git a/services/engine-25519/Cargo.toml b/services/engine-25519/Cargo.toml index 95fe1c60d..0729f04f2 100644 --- a/services/engine-25519/Cargo.toml +++ b/services/engine-25519/Cargo.toml @@ -7,16 +7,16 @@ description = "Curve25519 Engine" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} llio = {path = "../llio"} # temporary for testing some power stuff utralib = { version = "0.1.23", optional = true, default-features = false } -xous-ipc = "0.9.53" +xous-ipc = "0.9.55" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} diff --git a/services/engine-sha512/Cargo.toml b/services/engine-sha512/Cargo.toml index 281857828..c1e617378 100644 --- a/services/engine-sha512/Cargo.toml +++ b/services/engine-sha512/Cargo.toml @@ -7,15 +7,15 @@ description = "Sha512 hardware accelerator engine" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} trng = { path = "../trng" } -xous-ipc = "0.9.53" +xous-ipc = "0.9.55" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} diff --git a/services/ffi-test/Cargo.toml b/services/ffi-test/Cargo.toml index f4440f9d5..353a94732 100644 --- a/services/ffi-test/Cargo.toml +++ b/services/ffi-test/Cargo.toml @@ -7,14 +7,14 @@ description = "FFI test integration" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} ffi-sys = {path = "sys"} keyboard = {path = "../keyboard"} diff --git a/services/gam/Cargo.toml b/services/gam/Cargo.toml index ec9b4d837..dfa0f8fb8 100644 --- a/services/gam/Cargo.toml +++ b/services/gam/Cargo.toml @@ -14,18 +14,18 @@ ime-plugin-api = {path = "../ime-plugin-api"} ime-plugin-shell = {path = "../ime-plugin-shell"} keyboard = {path = "../keyboard"} log = "0.4.14" -log-server = {package = "xous-api-log", version = "0.1.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} +log-server = {package = "xous-api-log", version = "0.1.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} trng = {path = "../trng"} -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} -susres = {package = "xous-api-susres", version = "0.9.49"} # used for the sleep now menu item +susres = {package = "xous-api-susres", version = "0.9.51"} # used for the sleep now menu item enum_dispatch = "0.3.7" # used for trait-based dispatch off of multiple layout objects. locales = {path = "../../locales"} @@ -52,3 +52,4 @@ tts = [] # default = ["debugprint"] # "debugprint" default = [] ditherpunk = [] +unsafe-app-loading = [] # allow new contexts to be registered dynamically - this is unsafe and should not be set for release builds. diff --git a/services/gam/src/api.rs b/services/gam/src/api.rs index 9c6b12767..ff995df69 100644 --- a/services/gam/src/api.rs +++ b/services/gam/src/api.rs @@ -110,6 +110,12 @@ pub struct UxRegistration { /// If the LayoutType is not an App, this field is ignored and does nothing pub focuschange_id: Option, } +#[cfg(feature="unsafe-app-loading")] +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Copy, Clone)] +pub struct NameRegistration { + pub name: String::<128>, + pub auth_token: [u32; 4], +} #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] @@ -211,6 +217,10 @@ pub(crate) enum Opcode { /// Allow main menu activation. Used by the PDDB to turn ungate the main menu once it is mounted. /// This resolves race conditions that depend upon the PDDB configurations. AllowMainMenu = 33, + + /// Register a name that can acquire a token. This is only intended to be used with pre-registered apps + #[cfg(feature="unsafe-app-loading")] + RegisterName = 34, } // small wart -- we have to reset the size of a modal to max size for resize computations diff --git a/services/gam/src/contexts.rs b/services/gam/src/contexts.rs index 640d0d311..4ef21dcfd 100644 --- a/services/gam/src/contexts.rs +++ b/services/gam/src/contexts.rs @@ -130,6 +130,10 @@ impl ContextManager { pub(crate) fn claim_token(&mut self, name: &str) -> Option<[u32; 4]> { self.tm.claim_token(name) } + #[cfg(feature="unsafe-app-loading")] + pub(crate) fn register_name(&mut self, name: &str, auth_token: &[u32; 4]) { + self.tm.register_name(name, auth_token); + } pub(crate) fn allow_untrusted_code(&self) -> bool { self.tm.allow_untrusted_code() } diff --git a/services/gam/src/lib.rs b/services/gam/src/lib.rs index 9e351624c..3cf7c81ee 100644 --- a/services/gam/src/lib.rs +++ b/services/gam/src/lib.rs @@ -31,6 +31,7 @@ use ime_plugin_api::{ImefCallback, ApiToken}; #[doc = include_str!("../README.md")] +pub const RATE_LIMIT_MS: usize = 33; pub const SYSTEM_STYLE: GlyphStyle = GlyphStyle::Tall; // Add names here and insert them into the EXPECTED_BOOT_CONTEXTS structure below. @@ -154,6 +155,7 @@ impl Gam { tv.bounds_computed = tvr.bounds_computed; tv.cursor = tvr.cursor; tv.overflow = tvr.overflow; + tv.busy_animation_state = tvr.busy_animation_state; } api::Return::NotCurrentlyDrawable => { tv.bounds_computed = None; @@ -311,6 +313,16 @@ impl Gam { Ok(returned_claim.token) } + #[cfg(feature="unsafe-app-loading")] + pub fn register_name(&self, name: &str, auth_token: [u32; 4]) -> Result<(), xous::Error> { + let name_registration = NameRegistration { + name: String::<128>::from_str(name), + auth_token + }; + let buf = Buffer::into_buf(name_registration).or(Err(xous::Error::InternalError))?; + buf.lend(self.conn, Opcode::RegisterName.to_u32().unwrap()).or(Err(xous::Error::InternalError))?; + Ok(()) + } pub fn set_predictor_api_token(&self, api_token: [u32; 4], gam_token: [u32; 4]) -> Result<(), xous::Error> { let at = ApiToken { gam_token, diff --git a/services/gam/src/main.rs b/services/gam/src/main.rs index bf0bd19c9..d51370329 100644 --- a/services/gam/src/main.rs +++ b/services/gam/src/main.rs @@ -175,7 +175,7 @@ fn wrapped_main() -> ! { continue; // don't allow any redraws if a powerdown is requested } let elapsed_time = ticktimer.elapsed_ms(); - if elapsed_time - last_time > 33 { // rate limit updates, no point in going faster than the eye can see + if elapsed_time - last_time > gam::RATE_LIMIT_MS as u64 { // rate limit updates, no point in going faster than the eye can see last_time = elapsed_time; if deface(&gfx, &trng, &mut canvases) { @@ -246,6 +246,7 @@ fn wrapped_main() -> ! { tv.cursor = tv_clone.cursor; tv.bounds_computed = tv_clone.bounds_computed; tv.overflow = tv_clone.overflow; + tv.busy_animation_state = tv_clone.busy_animation_state; let ret = api::Return::RenderReturn(tv); buffer.replace(ret).unwrap(); @@ -287,6 +288,7 @@ fn wrapped_main() -> ! { tv.cursor = tv_clone.cursor; tv.bounds_computed = tv_clone.bounds_computed; tv.overflow = tv_clone.overflow; + tv.busy_animation_state = tv_clone.busy_animation_state; let ret = api::Return::RenderReturn(tv); buffer.replace(ret).unwrap(); @@ -567,7 +569,10 @@ fn wrapped_main() -> ! { // set before the leaving state due to interior-mutability issues (see the block under the debug statement // titled "resolving visibility rules"). The fix is to simply not issue a context switch if it's the same app. // It saves CPU thrashing and also works around this problem. (see issue #145) + #[cfg(not(feature="unsafe-app-loading"))] let authorized_switchers = vec![MAIN_MENU_NAME, ROOTKEY_MODAL_NAME, gam::STATUS_BAR_NAME]; + #[cfg(feature="unsafe-app-loading")] + let authorized_switchers = [&[MAIN_MENU_NAME, ROOTKEY_MODAL_NAME, gam::STATUS_BAR_NAME], gam::EXPECTED_APP_CONTEXTS].concat().to_vec(); for switchers in authorized_switchers { if let Some(auth_token) = context_mgr.find_app_token_by_name(switchers) { if auth_token == switchapp.token { @@ -670,6 +675,13 @@ fn wrapped_main() -> ! { context_mgr.allow_mainmenu(); xous::return_scalar(msg.sender, 0).ok(); } + #[cfg(feature="unsafe-app-loading")] + Some(Opcode::RegisterName) => { + let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; + let registration = buffer.to_original::().unwrap(); + gfx.set_devboot(true).ok(); // indicate to users that we are no longer in a codebase that is exclusively trusted code + context_mgr.register_name(registration.name.to_str(), ®istration.auth_token); + } Some(Opcode::Quit) => break, None => {log::error!("unhandled message {:?}", msg);} } diff --git a/services/gam/src/tokens.rs b/services/gam/src/tokens.rs index ce8f2fde1..083629ae0 100644 --- a/services/gam/src/tokens.rs +++ b/services/gam/src/tokens.rs @@ -18,18 +18,26 @@ pub(crate) struct NamedToken { } pub(crate) struct TokenManager { tokens: Vec::, + #[cfg(feature="unsafe-app-loading")] + extra_names: Vec, trng: trng::Trng, } impl<'a> TokenManager { pub(crate) fn new(xns: &xous_names::XousNames) -> TokenManager { TokenManager { tokens: Vec::new(), + #[cfg(feature="unsafe-app-loading")] + extra_names: Vec::new(), trng: trng::Trng::new(&xns).unwrap(), } } /// checks to see if all the slots have been occupied. We can't allow untrusted code to run until all slots have checked in pub(crate) fn allow_untrusted_code(&self) -> bool { - if self.tokens.len() == (EXPECTED_BOOT_CONTEXTS.len() + EXPECTED_APP_CONTEXTS.len()) { + #[cfg(feature="unsafe-app-loading")] + let expected_len = EXPECTED_BOOT_CONTEXTS.len() + EXPECTED_APP_CONTEXTS.len() + self.extra_names.len(); + #[cfg(not(feature="unsafe-app-loading"))] + let expected_len = EXPECTED_BOOT_CONTEXTS.len() + EXPECTED_APP_CONTEXTS.len(); + if self.tokens.len() == expected_len { true } else { // throw a bone to the dev who has to debug this error. This typically only triggers after a major @@ -51,6 +59,10 @@ impl<'a> TokenManager { if EXPECTED_APP_CONTEXTS.iter().find(|&&context| context == name).is_some() { found = true; } + #[cfg(feature="unsafe-app-loading")] + if self.extra_names.iter().find(|&context| context == name).is_some() { + found = true; + } if !found { log::error!("Server {} is not pre-registered in gam/lib.rs/EXPECTED_BOOT_CONTEXTS or apps.rs/EXPECTED_APP_CONTEXTS. Did you forget to register it?", name); return None @@ -90,4 +102,21 @@ impl<'a> TokenManager { } None } + /// Register a new name that can then claim a token. Note that only pre-registered applications are allowed to do this. + #[cfg(feature="unsafe-app-loading")] + pub(crate) fn register_name(&mut self, name: &str, auth_token: &[u32; 4]) { + if let Some(registrant) = self.lookup_name(auth_token) { + if EXPECTED_BOOT_CONTEXTS.iter() + .find( + |&&context| context == registrant).is_some() + || EXPECTED_APP_CONTEXTS.iter() + .find(|&&context| context == registrant).is_some() { + self.extra_names.push(name.to_string()); + } else { + log::error!("`{}' does not have permission to register a new name because it is not pre-registered", registrant); + } + } else { + log::error!("Token {:?} does not correspond with a name", auth_token); + } + } } diff --git a/services/graphics-server/Cargo.toml b/services/graphics-server/Cargo.toml index 4df01a221..5aea6e782 100644 --- a/services/graphics-server/Cargo.toml +++ b/services/graphics-server/Cargo.toml @@ -9,14 +9,14 @@ version = "0.1.0" [dependencies] keyboard = {path = "../keyboard"} log = "0.4.14" -log-server = {package = "xous-api-log", version = "0.1.49"} -xous = "0.9.53" -susres = {package = "xous-api-susres", version = "0.9.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} +log-server = {package = "xous-api-log", version = "0.1.51"} +xous = "0.9.55" +susres = {package = "xous-api-susres", version = "0.9.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} locales = {path = "../../locales"} -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} diff --git a/services/graphics-server/src/api/text.rs b/services/graphics-server/src/api/text.rs index 0a10c2f19..6b9bea76b 100644 --- a/services/graphics-server/src/api/text.rs +++ b/services/graphics-server/src/api/text.rs @@ -79,6 +79,7 @@ impl From for TextOp { // roughly 168 bytes to represent the rest of the struct, and we want to fill out the 4096 byte page with text const TEXTVIEW_LEN: usize = 3072; +pub const TEXTVIEW_DEFAULT_STYLE: GlyphStyle = GlyphStyle::Regular; #[derive(Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct TextView { @@ -113,6 +114,9 @@ pub struct TextView { // this field specifies the beginning and end of a "selected" region of text pub selected: Option<[u32; 2]>, + // this field tracks the state of a busy animation, if `Some` + pub busy_animation_state: Option, + pub text: String<3072>, } impl TextView { @@ -126,7 +130,7 @@ impl TextView { clip_rect: None, bounds_hint, bounds_computed: None, - style: GlyphStyle::Regular, + style: TEXTVIEW_DEFAULT_STYLE, text: String::<3072>::new(), cursor: Cursor::new(0, 0, 0), insertion: None, @@ -139,6 +143,7 @@ impl TextView { clear_area: true, overflow: None, dry_run: false, + busy_animation_state: None, } } pub fn dry_run(&self) -> bool { diff --git a/services/graphics-server/src/lib.rs b/services/graphics-server/src/lib.rs index 416b7e352..5908e2950 100644 --- a/services/graphics-server/src/lib.rs +++ b/services/graphics-server/src/lib.rs @@ -157,6 +157,7 @@ impl Gfx { tv.bounds_computed = tvr.bounds_computed; tv.cursor = tvr.cursor; tv.overflow = tvr.overflow; + tv.busy_animation_state = tvr.busy_animation_state; Ok(()) } diff --git a/services/graphics-server/src/main.rs b/services/graphics-server/src/main.rs index 8ce93dfaa..b04764026 100644 --- a/services/graphics-server/src/main.rs +++ b/services/graphics-server/src/main.rs @@ -189,7 +189,7 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! { op::circle(display.native_buffer(), circ, Some(obj.clip)); } ClipObjectType::Rect(rect) => { - op::rectangle(display.native_buffer(), rect, Some(obj.clip)); + op::rectangle(display.native_buffer(), rect, Some(obj.clip), false); } ClipObjectType::RoundRect(rr) => { op::rounded_rectangle(display.native_buffer(), rr, Some(obj.clip)); @@ -217,7 +217,7 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! { op::circle(display.native_buffer(), circ, Some(obj.clip)); } ClipObjectType::Rect(rect) => { - op::rectangle(display.native_buffer(), rect, Some(obj.clip)); + op::rectangle(display.native_buffer(), rect, Some(obj.clip), false); } ClipObjectType::RoundRect(rr) => { op::rounded_rectangle(display.native_buffer(), rr, Some(obj.clip)); @@ -323,13 +323,25 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! { r } _ => { - // composition_top_left already had a screen_offset added when it was computed. just margin it out - let mut r = Rectangle::new( - composition_top_left, - composition_top_left.add(Point::new(composition.bb_width() as _, composition.bb_height() as _)) - ); - r.margin_out(tv.margin); - r + if tv.busy_animation_state.is_some() { + // we want to clear the entire potentially drawable region, not just the dirty box if we're + // doing a busy animation. + let r = Rectangle::new( + Point::new(screen_offset.x, composition_top_left.y), + Point::new(screen_offset.x, composition_top_left.y).add( + Point::new(typeset_extent.x, composition.bb_height()) + ) + ); + r + } else { + // composition_top_left already had a screen_offset added when it was computed. just margin it out + let mut r = Rectangle::new( + composition_top_left, + composition_top_left.add(Point::new(composition.bb_width() as _, composition.bb_height() as _)) + ); + r.margin_out(tv.margin); + r + } } }; @@ -370,7 +382,7 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! { tv.clip_rect, ); } else { - op::rectangle(display.native_buffer(), clear_rect, tv.clip_rect); + op::rectangle(display.native_buffer(), clear_rect, tv.clip_rect, false); } } // for now, if we're in braille mode, emit all text to the debug log so we can see it @@ -384,6 +396,55 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! { .unwrap_or(Rectangle::new(Point::new(0, 0), Point::new(0, 0,))); composition.render(display.native_buffer(), composition_top_left, tv.invert, smallest_rect); } + + // run the busy animation + if let Some(state) = tv.busy_animation_state { + let total_width = typeset_extent.x as i16; + if total_width > op::BUSY_ANIMATION_RECT_WIDTH * 2 { + let step = state as i16 % (op::BUSY_ANIMATION_RECT_WIDTH * 2); + for offset in (0..(total_width + op::BUSY_ANIMATION_RECT_WIDTH * 2)) + .step_by((op::BUSY_ANIMATION_RECT_WIDTH * 2) as usize) { + let left_x = offset + step + composition_top_left.x; + if offset == 0 && (step >= op::BUSY_ANIMATION_RECT_WIDTH) && (step < op::BUSY_ANIMATION_RECT_WIDTH * 2) { + // handle the truncated "left" rectangle + let mut trunc_rect = Rectangle::new( + Point::new(composition_top_left.x as i16, clear_rect.tl().y), + Point::new((step + composition_top_left.x - op::BUSY_ANIMATION_RECT_WIDTH) as i16, clear_rect.br().y) + ); + trunc_rect.style = DrawStyle { + fill_color: Some(PixelColor::Light), + stroke_color: None, + stroke_width: 0, + }; + op::rectangle( + display.native_buffer(), + trunc_rect, + tv.clip_rect, + true + ); + } // the "right" rectangle is handled by the clipping mask + let mut xor_rect = Rectangle::new( + Point::new(left_x as i16, clear_rect.tl().y), + Point::new((left_x + op::BUSY_ANIMATION_RECT_WIDTH) as i16, clear_rect.br().y) + ); + xor_rect.style = DrawStyle { + fill_color: Some(PixelColor::Light), + stroke_color: None, + stroke_width: 0, + }; + op::rectangle( + display.native_buffer(), + xor_rect, + tv.clip_rect, + true + ); + } + } else { + // don't do the animation, this could be abused to create inverted text + } + tv.busy_animation_state = Some(state + 2); + } + // type mismatch for now, replace this with a simple equals once we sort that out tv.cursor.pt.x = composition.final_cursor().pt.x; tv.cursor.pt.y = composition.final_cursor().pt.y; @@ -404,7 +465,7 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! { Some(Opcode::Clear) => { let mut r = Rectangle::full_screen(); r.style = DrawStyle::new(PixelColor::Light, PixelColor::Light, 0); - op::rectangle(display.native_buffer(), r, screen_clip.into()) + op::rectangle(display.native_buffer(), r, screen_clip.into(), false) } Some(Opcode::Line) => msg_scalar_unpack!(msg, p1, p2, style, _, { let l = @@ -417,7 +478,7 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! { Point::from(br), DrawStyle::from(style), ); - op::rectangle(display.native_buffer(), r, screen_clip.into()); + op::rectangle(display.native_buffer(), r, screen_clip.into(), false); }), Some(Opcode::RoundedRectangle) => msg_scalar_unpack!(msg, tl, br, style, r, { let rr = RoundedRectangle::new( diff --git a/services/graphics-server/src/op.rs b/services/graphics-server/src/op.rs index 5b7e26f8b..f293203b4 100644 --- a/services/graphics-server/src/op.rs +++ b/services/graphics-server/src/op.rs @@ -12,6 +12,9 @@ pub const HEIGHT: i16 = 536; /// For passing frame buffer references pub type LcdFB = [u32; LCD_FRAME_BUF_SIZE]; +/// Set the expected rectangle length for the busy animation +pub const BUSY_ANIMATION_RECT_WIDTH: i16 = 32; // golden ratio off of a height of 20 + fn put_pixel(fb: &mut LcdFB, x: i16, y: i16, color: PixelColor) { let mut clip_y: usize = y as usize; if clip_y >= LCD_LINES { @@ -254,7 +257,7 @@ impl Iterator for RectangleIterator { } } -pub fn rectangle(fb: &mut LcdFB, rect: Rectangle, clip: Option) { +pub fn rectangle(fb: &mut LcdFB, rect: Rectangle, clip: Option, xor: bool) { let r = RectangleIterator { top_left: rect.tl, bottom_right: rect.br, @@ -264,7 +267,24 @@ pub fn rectangle(fb: &mut LcdFB, rect: Rectangle, clip: Option) { }; for pixel in r { - put_pixel(fb, pixel.0.x, pixel.0.y, pixel.1); + if !xor { + put_pixel(fb, pixel.0.x, pixel.0.y, pixel.1); + } else { + if rect.width() <= BUSY_ANIMATION_RECT_WIDTH as u32 { + // only allow xor of a rectangle if it is within the expected width for the busy animation + // This is to make it annoying for someone using an XOR rectangle to synthesize inverted + // text on an otherwise untrusted dialog box (I suspect it's not impossible, but introduces + // a likelihood of drawing artifacts especially across translations and font size selections). + if pixel.0.y % 25 != 5 { + xor_pixel(fb, pixel.0.x, pixel.0.y); + } else { + // don't allow XOR on some pixels to avoid this being used as a primitive to synthesize secure boxes + } + } else { + log::warn!("invalid xor rect width"); + put_pixel(fb, pixel.0.x, pixel.0.y, pixel.1); + } + } } } diff --git a/services/graphics-server/src/testing.rs b/services/graphics-server/src/testing.rs index 0234de717..8ea7e7635 100644 --- a/services/graphics-server/src/testing.rs +++ b/services/graphics-server/src/testing.rs @@ -5,14 +5,15 @@ use num_traits::*; #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] enum TestType { - BoundingBox = 0, - LowerRight = 1, - LowerLeft = 2, - TopLeft = 3, - TopRight = 4, - Overflow = 5, - Insert = 6, - End = 7, + BusyAnimation = 9, + BoundingBox = 10, + LowerRight = 11, + LowerLeft = 12, + TopLeft = 13, + TopRight = 14, + Overflow = 15, + Insert = 16, + End = 17, } const TEST_STYLE: GlyphStyle = GlyphStyle::Tall; pub fn tests() { @@ -22,8 +23,8 @@ pub fn tests() { let gfx = graphics_server::Gfx::new(&xns).unwrap(); let ticktimer = ticktimer_server::Ticktimer::new().expect("Couldn't connect to Ticktimer"); - for index in TestType::BoundingBox.to_usize().unwrap()..TestType::End.to_usize().unwrap() { - // show a black screen + for index in TestType::BusyAnimation.to_usize().unwrap()..TestType::End.to_usize().unwrap() { + // pause between each tests ticktimer.sleep_ms(1000).unwrap(); // draw a black screen let screensize = gfx.screen_size().expect("Couldn't get screen size"); @@ -50,6 +51,29 @@ pub fn tests() { gfx.flush().unwrap(); match FromPrimitive::from_usize(index) { + Some(TestType::BusyAnimation) => { + let anim_rect = Rectangle::new_coords(10, 10, 240, 30); + let mut tv = TextView::new(Gid::new([0, 0, 0, 0]), + TextBounds::BoundingBox(anim_rect)); + tv.clip_rect = Some(clipping_area); + tv.style = GlyphStyle::Small; + tv.ellipsis = false; + write!(tv, "Test of busy animation").unwrap(); + tv.insertion = None; + tv.draw_border = false; + tv.busy_animation_state = Some(0); + for y in 0..30 { + gfx.draw_rectangle(checkbound).unwrap(); + tv.bounds_hint = TextBounds::BoundingBox(Rectangle::new_coords( + 10, 10 + y, 240, 30 + y + )); + for _ in 0..5 { + gfx.draw_textview(&mut tv).unwrap(); + gfx.flush().unwrap(); + ticktimer.sleep_ms(100).unwrap(); + } + } + } Some(TestType::BoundingBox) => { let mut tv = TextView::new(Gid::new([0, 0, 0, 0]), TextBounds::BoundingBox( diff --git a/services/ime-frontend/Cargo.toml b/services/ime-frontend/Cargo.toml index 8c0d8d3d5..c30f1b678 100644 --- a/services/ime-frontend/Cargo.toml +++ b/services/ime-frontend/Cargo.toml @@ -12,12 +12,12 @@ graphics-server = {path = "../graphics-server"} ime-plugin-api = {path = "../ime-plugin-api"} keyboard = {path = "../keyboard"} log = "0.4.14" -log-server = {package = "xous-api-log", version = "0.1.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} -xous = "0.9.53" +log-server = {package = "xous-api-log", version = "0.1.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} +xous = "0.9.55" locales = {path = "../../locales"} -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/services/ime-plugin-api/Cargo.toml b/services/ime-plugin-api/Cargo.toml index 6625c07d9..f11606422 100644 --- a/services/ime-plugin-api/Cargo.toml +++ b/services/ime-plugin-api/Cargo.toml @@ -9,9 +9,9 @@ version = "0.1.0" [dependencies] graphics-server = {path = "../graphics-server"}# this is used by the IMEF portion of the API log = "0.4.14" -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"}# used by the IMEF for registering listeners +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"}# used by the IMEF for registering listeners num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/services/ime-plugin-shell/Cargo.toml b/services/ime-plugin-shell/Cargo.toml index 17a2d128e..e5fd1ac16 100644 --- a/services/ime-plugin-shell/Cargo.toml +++ b/services/ime-plugin-shell/Cargo.toml @@ -9,11 +9,11 @@ version = "0.1.0" [dependencies] ime-plugin-api = {path = "../ime-plugin-api"} log = "0.4.14" -log-server = {package = "xous-api-log", version = "0.1.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} +log-server = {package = "xous-api-log", version = "0.1.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/services/ime-plugin-tts/Cargo.toml b/services/ime-plugin-tts/Cargo.toml index 82a21c47c..30fe3eb8f 100644 --- a/services/ime-plugin-tts/Cargo.toml +++ b/services/ime-plugin-tts/Cargo.toml @@ -9,11 +9,11 @@ version = "0.1.0" [dependencies] ime-plugin-api = {path = "../ime-plugin-api"} log = "0.4.14" -log-server = {package = "xous-api-log", version = "0.1.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} +log-server = {package = "xous-api-log", version = "0.1.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/services/jtag/Cargo.toml b/services/jtag/Cargo.toml index 42e71a1f9..aaf888248 100644 --- a/services/jtag/Cargo.toml +++ b/services/jtag/Cargo.toml @@ -7,16 +7,16 @@ description = "JTAG port server" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} -xous-ipc = "0.9.53" +xous-ipc = "0.9.55" rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} utralib = { version = "0.1.23", optional = true, default-features = false } diff --git a/services/jtag/src/efuse_api.rs b/services/jtag/src/efuse_api.rs index 7e01ebc16..5f24ec381 100644 --- a/services/jtag/src/efuse_api.rs +++ b/services/jtag/src/efuse_api.rs @@ -52,10 +52,10 @@ const CMD_FUSE_CNTL: u32 = 0b110100; impl EfusePhy { pub fn new() -> Self { EfusePhy { - /// bank mapping as follows: - /// 0 - config - /// 1-11 - key (11 shared with user LSB) - /// 12 - user + // bank mapping as follows: + // 0 - config + // 1-11 - key (11 shared with user LSB) + // 12 - user banks: [0; FUSE_BANKS], key: [0; 32], user: 0, diff --git a/services/kernel-test/Cargo.toml b/services/kernel-test/Cargo.toml index 72e554ee8..172141dbc 100644 --- a/services/kernel-test/Cargo.toml +++ b/services/kernel-test/Cargo.toml @@ -7,9 +7,9 @@ description = "Program to test various aspects of the kernel" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } log = "0.4.14" utralib = { version = "0.1.23", optional = true, default-features = false } diff --git a/services/keyboard/Cargo.toml b/services/keyboard/Cargo.toml index 0e61d23a2..789bdbb37 100644 --- a/services/keyboard/Cargo.toml +++ b/services/keyboard/Cargo.toml @@ -8,13 +8,13 @@ version = "0.1.0" # Dependency versions enforced by Cargo.lock. [dependencies] log = {version = "0.4", features = ["max_level_trace", "release_max_level_trace"]} -log-server = {package = "xous-api-log", version = "0.1.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} +log-server = {package = "xous-api-log", version = "0.1.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} llio = {path = "../llio"} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} spinor = {path = "../spinor"} num-derive = {version = "0.3.3", default-features = false} diff --git a/services/libstd-test/Cargo.toml b/services/libstd-test/Cargo.toml index 4da8658f3..d5051d042 100644 --- a/services/libstd-test/Cargo.toml +++ b/services/libstd-test/Cargo.toml @@ -7,10 +7,10 @@ description = "libstd test stub" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/services/llio/Cargo.toml b/services/llio/Cargo.toml index f92ba9f84..c5e471931 100644 --- a/services/llio/Cargo.toml +++ b/services/llio/Cargo.toml @@ -8,12 +8,12 @@ version = "0.1.0" # Dependency versions enforced by Cargo.lock. [dependencies] log = "0.4.14" -log-server = {package = "xous-api-log", version = "0.1.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} -susres = {package = "xous-api-susres", version = "0.9.49"} +log-server = {package = "xous-api-log", version = "0.1.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} +susres = {package = "xous-api-susres", version = "0.9.51"} # RTC dependencies bitflags = "1.2.1" diff --git a/services/log-test-client/Cargo.toml b/services/log-test-client/Cargo.toml index 32cc01afb..c3c4b1b88 100644 --- a/services/log-test-client/Cargo.toml +++ b/services/log-test-client/Cargo.toml @@ -7,7 +7,7 @@ version = "0.1.0" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" +xous = "0.9.55" [features] default = [] diff --git a/services/modals/Cargo.toml b/services/modals/Cargo.toml index 725fc50e6..eeabd1d66 100644 --- a/services/modals/Cargo.toml +++ b/services/modals/Cargo.toml @@ -7,14 +7,14 @@ description = "Ux modals server" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -xous-ipc = "0.9.53" +xous-ipc = "0.9.55" rkyv = {version = "0.4.3", features = ["const_generics"], default-features = false} gam = {path="../gam"} trng = {path="../trng"} diff --git a/services/net/Cargo.toml b/services/net/Cargo.toml index 8ce0db2c0..05475eda1 100644 --- a/services/net/Cargo.toml +++ b/services/net/Cargo.toml @@ -7,15 +7,15 @@ description = "Network middleware" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} #rkyv = "0.7.18" rkyv = {version = "0.4.3", features = ["const_generics"], default-features = false} llio = {path = "../llio"} diff --git a/services/net/src/tests.rs b/services/net/src/tests.rs index 743ce0157..c1bd41492 100644 --- a/services/net/src/tests.rs +++ b/services/net/src/tests.rs @@ -47,7 +47,7 @@ fn bind_error() { #[test] fn connect_error() { - match TcpStream::connect("0.0.0.0:1") { + match TcpStream::connect(("0.0.0.0",1)) { Ok(..) => panic!(), Err(e) => assert!( e.kind() == ErrorKind::ConnectionRefused diff --git a/services/pddb/Cargo.toml b/services/pddb/Cargo.toml index 97320cc8a..a0375f008 100644 --- a/services/pddb/Cargo.toml +++ b/services/pddb/Cargo.toml @@ -8,12 +8,12 @@ description = "Plausibly Deniable Database" # Dependency versions enforced by Cargo.lock. [dependencies] bitflags = {version = "1"} -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } -susres = {package = "xous-api-susres", version = "0.9.49"} +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } +susres = {package = "xous-api-susres", version = "0.9.51"} log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/services/pddb/src/lib.rs b/services/pddb/src/lib.rs index 84d2e5d22..a568e3417 100644 --- a/services/pddb/src/lib.rs +++ b/services/pddb/src/lib.rs @@ -115,7 +115,7 @@ impl Pddb { cb_handle: RefCell::new(None), keys, trng: trng::Trng::new(&xns).unwrap(), - /// These are record the result of the most recent call to list_keys() + // These are record the result of the most recent call to list_keys() key_count: RefCell::new(0), found_key_count: RefCell::new(0), } diff --git a/services/root-keys/Cargo.toml b/services/root-keys/Cargo.toml index 2673a09db..fa6766d1e 100644 --- a/services/root-keys/Cargo.toml +++ b/services/root-keys/Cargo.toml @@ -7,12 +7,12 @@ description = "Xous root keys server" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} trng = {path= "../trng"} spinor = {path="../spinor"} llio = {path="../llio"} @@ -20,7 +20,7 @@ com = {path="../com"} xous-semver = "0.1.2" utralib = { version = "0.1.23", optional = true, default-features = false } -xous-ipc = "0.9.53" +xous-ipc = "0.9.55" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} diff --git a/services/shellchat/Cargo.toml b/services/shellchat/Cargo.toml index 565d3dc8f..fdf0d6539 100644 --- a/services/shellchat/Cargo.toml +++ b/services/shellchat/Cargo.toml @@ -16,13 +16,13 @@ ime-plugin-shell = {path = "../ime-plugin-shell"} ime-plugin-tts = {path = "../ime-plugin-tts"} llio = {path = "../llio"} log = "0.4.14" -log-server = {package = "xous-api-log", version = "0.1.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} +log-server = {package = "xous-api-log", version = "0.1.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} keyboard = {path = "../keyboard"} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} codec = {path = "../codec"} #engine-sha512 = {path="../engine-sha512"} sha2 = {path="../engine-sha512"} diff --git a/services/shellchat/src/nettests.rs b/services/shellchat/src/nettests.rs index d634080c0..fccfd4a78 100644 --- a/services/shellchat/src/nettests.rs +++ b/services/shellchat/src/nettests.rs @@ -180,7 +180,7 @@ fn bind_error() { } fn connect_error() { - match TcpStream::connect("0.0.0.0:1") { + match TcpStream::connect(("0.0.0.0",1)) { Ok(..) => panic!(), Err(e) => { log::info!("connect_error is {:?}", e); diff --git a/services/skeleton/Cargo.toml b/services/skeleton/Cargo.toml index 577f05217..f8fba0115 100644 --- a/services/skeleton/Cargo.toml +++ b/services/skeleton/Cargo.toml @@ -7,14 +7,14 @@ description = "Audio CODEC server" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} utralib = { version = "0.1.23", optional = true, default-features = false } diff --git a/services/spinor/Cargo.toml b/services/spinor/Cargo.toml index 3809b6142..829d9b490 100644 --- a/services/spinor/Cargo.toml +++ b/services/spinor/Cargo.toml @@ -7,18 +7,18 @@ description = "SPINOR ROM operations server" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} trng = { path = "../trng" } com = { path = "../com" } llio = { path = "../llio" } rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} -xous-ipc = "0.9.53" +xous-ipc = "0.9.55" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/services/status/Cargo.toml b/services/status/Cargo.toml index 29a89f4bf..1ff2a48a0 100644 --- a/services/status/Cargo.toml +++ b/services/status/Cargo.toml @@ -10,17 +10,17 @@ version = "0.1.0" com = {path = "../com"} content-plugin-api = {path = "../content-plugin-api"} log = "0.4.14" -log-server = {package = "xous-api-log", version = "0.1.49"} -ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.49"} +log-server = {package = "xous-api-log", version = "0.1.51"} +ticktimer-server = {package = "xous-api-ticktimer", version = "0.9.51"} trng = {path = "../trng"} llio = {path = "../llio"} -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} graphics-server = {path = "../graphics-server"} gam = {path = "../gam"} locales = {path = "../../locales"} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} root-keys = {path = "../root-keys"} modals = {path = "../modals"} pddb = {path = "../pddb"} diff --git a/services/test-spawn/Cargo.toml b/services/test-spawn/Cargo.toml index 4697ba6aa..3a6887d53 100644 --- a/services/test-spawn/Cargo.toml +++ b/services/test-spawn/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -xous = "0.9.53" +xous = "0.9.55" diff --git a/services/test-spawn/spawn/Cargo.toml b/services/test-spawn/spawn/Cargo.toml index 980afd547..0227ad0dc 100644 --- a/services/test-spawn/spawn/Cargo.toml +++ b/services/test-spawn/spawn/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -xous = "0.9.53" +xous = "0.9.55" diff --git a/services/ticktimer-test-client/Cargo.toml b/services/ticktimer-test-client/Cargo.toml index 82efc785b..5600b6631 100644 --- a/services/ticktimer-test-client/Cargo.toml +++ b/services/ticktimer-test-client/Cargo.toml @@ -7,9 +7,9 @@ version = "0.1.0" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" +xous = "0.9.55" log = "0.4.14" -log-server = {package = "xous-api-log", version = "0.1.49"} +log-server = {package = "xous-api-log", version = "0.1.51"} [features] default = [] diff --git a/services/trng/Cargo.toml b/services/trng/Cargo.toml index f6082174e..32897d21e 100644 --- a/services/trng/Cargo.toml +++ b/services/trng/Cargo.toml @@ -7,16 +7,16 @@ description = "TRNG server" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} -xous-ipc = "0.9.53" +xous-ipc = "0.9.55" rand_core = "0.5.1" # the 0.5.1 API is necessary for compatibility with curve25519-dalek crates utralib = { version = "0.1.23", optional = true, default-features = false } diff --git a/services/tts/Cargo.toml b/services/tts/Cargo.toml index 47a3690dc..7080dc04e 100644 --- a/services/tts/Cargo.toml +++ b/services/tts/Cargo.toml @@ -7,11 +7,11 @@ description = "Text to speech integration server" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} diff --git a/services/usb-device-xous/Cargo.toml b/services/usb-device-xous/Cargo.toml index da6e94661..87e9b1d6c 100644 --- a/services/usb-device-xous/Cargo.toml +++ b/services/usb-device-xous/Cargo.toml @@ -7,17 +7,17 @@ description = "Xous USB device drivers" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -xous-ipc = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" llio = {path="../llio"} num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} modals = {path = "../modals"} keyboard = {path = "../keyboard", features=["inject-api"]} bitfield = "0.13.2" @@ -26,10 +26,10 @@ xous-semver = "0.1.2" utralib = { version = "0.1.23", optional = true, default-features = false } trng = {path="../trng"} -usbd-human-interface-device = "0.2.1" +xous-usb-hid = {git = "https://github.com/betrusted-io/xous-usb-hid.git"} embedded-time = "0.12.1" # required by the keyboard interface -packed_struct = { version = "0.10", default-features = false } # used by the usbd-human-interface-device crate -num_enum = { version = "0.5", default-features = false } # used by the usbd-human-interface-device crate +packed_struct = { version = "0.10", default-features = false } # used by the xous-usb-hid crate +num_enum = { version = "0.5", default-features = false } # used by the xous-usb-hid crate # Mass storage support usbd_scsi = { path = "../../libs/mass-storage/usbd_scsi", optional = true } diff --git a/services/usb-device-xous/src/lib.rs b/services/usb-device-xous/src/lib.rs index f63389247..b9e57f9eb 100644 --- a/services/usb-device-xous/src/lib.rs +++ b/services/usb-device-xous/src/lib.rs @@ -6,11 +6,11 @@ use trng::api::TrngTestMode; use xous::{CID, send_message, Message}; use num_traits::*; pub use usb_device::device::UsbDeviceState; -pub use usbd_human_interface_device::device::keyboard::KeyboardLedsReport; -pub use usbd_human_interface_device::page::Keyboard as UsbKeyCode; +pub use xous_usb_hid::device::keyboard::KeyboardLedsReport; +pub use xous_usb_hid::page::Keyboard as UsbKeyCode; use packed_struct::PackedStruct; use xous_ipc::Buffer; -pub use usbd_human_interface_device::device::fido::RawFidoMsg; +pub use xous_usb_hid::device::fido::RawFidoReport; #[derive(Debug)] pub struct UsbHid { @@ -283,7 +283,7 @@ impl UsbHid { _ => panic!("Internal error: illegal return type"), } } - pub fn u2f_wait_incoming(&self) -> Result { + pub fn u2f_wait_incoming(&self) -> Result { let req = U2fMsgIpc { data: [0; 64], code: U2fCode::RxWait, @@ -294,7 +294,7 @@ impl UsbHid { let ack = buf.to_original::().unwrap(); match ack.code { U2fCode::RxAck => { - let mut u2fmsg = RawFidoMsg::default(); + let mut u2fmsg = RawFidoReport::default(); u2fmsg.packet.copy_from_slice(&ack.data); Ok(u2fmsg) }, @@ -308,7 +308,7 @@ impl UsbHid { } } /// Note: this variant is not tested. - pub fn u2f_wait_incoming_timeout(&self, timeout_ms: u64) -> Result { + pub fn u2f_wait_incoming_timeout(&self, timeout_ms: u64) -> Result { let req = U2fMsgIpc { data: [0; 64], code: U2fCode::RxWait, @@ -319,7 +319,7 @@ impl UsbHid { let ack = buf.to_original::().unwrap(); match ack.code { U2fCode::RxAck => { - let mut u2fmsg = RawFidoMsg::default(); + let mut u2fmsg = RawFidoReport::default(); u2fmsg.packet.copy_from_slice(&ack.data); Ok(u2fmsg) }, @@ -332,7 +332,7 @@ impl UsbHid { _ => Err(xous::Error::InternalError) } } - pub fn u2f_send(&self, msg: RawFidoMsg) -> Result<(), xous::Error> { + pub fn u2f_send(&self, msg: RawFidoReport) -> Result<(), xous::Error> { let mut req = U2fMsgIpc { data: [0; 64], code: U2fCode::Tx, diff --git a/services/usb-device-xous/src/main_hosted.rs b/services/usb-device-xous/src/main_hosted.rs index 2038c4b6e..96f230a2f 100644 --- a/services/usb-device-xous/src/main_hosted.rs +++ b/services/usb-device-xous/src/main_hosted.rs @@ -1,7 +1,7 @@ use crate::*; use num_traits::*; -use usbd_human_interface_device::device::fido::RawFidoMsg; +use xous_usb_hid::device::fido::RawFidoReport; use xous::{msg_scalar_unpack, msg_blocking_scalar_unpack}; use xous_semver::SemVer; use core::num::NonZeroU8; @@ -172,7 +172,7 @@ pub(crate) fn main_hosted() -> ! { let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut u2f_ipc = buffer.to_original::().unwrap(); if fido_listener_pid == msg.sender.pid() { - let mut u2f_msg = RawFidoMsg::default(); + let mut u2f_msg = RawFidoReport::default(); assert_eq!(u2f_ipc.code, U2fCode::Tx, "Expected U2fCode::Tx in wrapper"); u2f_msg.packet.copy_from_slice(&u2f_ipc.data); log::debug!("sent U2F packet {:x?}", u2f_ipc.data); diff --git a/services/usb-device-xous/src/main_hw.rs b/services/usb-device-xous/src/main_hw.rs index 636d8ae36..275942a81 100644 --- a/services/usb-device-xous/src/main_hw.rs +++ b/services/usb-device-xous/src/main_hw.rs @@ -3,8 +3,10 @@ use crate::*; use num_traits::*; use usb_device_xous::KeyboardLedsReport; use usb_device_xous::UsbDeviceType; -use usbd_human_interface_device::device::fido::RawFidoMsg; -use usbd_human_interface_device::device::fido::RawFidoInterface; +use xous_usb_hid::device::fido::RawFidoReport; +use xous_usb_hid::device::fido::RawFidoConfig; +use xous_usb_hid::device::fido::RawFido; +use xous_usb_hid::device::DeviceClass; use xous::{msg_scalar_unpack, msg_blocking_scalar_unpack}; #[cfg(not(feature="minimal"))] use xous_semver::SemVer; @@ -15,12 +17,10 @@ use utralib::generated::*; use usb_device::prelude::*; use usb_device::class_prelude::*; -use usbd_human_interface_device::page::Keyboard; -use usbd_human_interface_device::device::keyboard::NKROBootKeyboardInterface; -use usbd_human_interface_device::prelude::*; -use num_enum::FromPrimitive as EnumFromPrimitive; +use xous_usb_hid::page::Keyboard; +use xous_usb_hid::device::keyboard::{NKROBootKeyboard, NKROBootKeyboardConfig}; +use xous_usb_hid::prelude::*; -use embedded_time::Clock; use std::convert::TryInto; #[cfg(not(feature="minimal"))] use keyboard::KeyMap; @@ -29,24 +29,6 @@ use std::collections::VecDeque; use usbd_serial::SerialPort; -pub struct EmbeddedClock { - start: std::time::Instant, -} -impl EmbeddedClock { - pub fn new() -> EmbeddedClock { - EmbeddedClock { start: std::time::Instant::now() } - } -} - -impl Clock for EmbeddedClock { - type T = u64; - const SCALING_FACTOR: embedded_time::fraction::Fraction = ::new(1, 1_000); - - fn try_now(&self) -> Result, embedded_time::clock::Error> { - Ok(embedded_time::Instant::new(self.start.elapsed().as_millis().try_into().unwrap())) - } -} - /// Time allowed for switchover between device core types. It's longer because some hosts /// get really confused when you have the same VID/PID show up with a different set of endpoints. const EXTENDED_CORE_RESET_MS: usize = 4000; @@ -228,14 +210,13 @@ pub(crate) fn main_hw() -> ! { // FIDO + keyboard let usb_alloc = UsbBusAllocator::new(usb_fidokbd_dev); - let clock = EmbeddedClock::new(); let mut composite = UsbHidClassBuilder::new() - .add_interface( - NKROBootKeyboardInterface::default_config(&clock), + .add_device( + NKROBootKeyboardConfig::default(), ) - .add_interface( - RawFidoInterface::default_config() + .add_device( + RawFidoConfig::default() ) .build(&usb_alloc); @@ -248,8 +229,8 @@ pub(crate) fn main_hw() -> ! { // FIDO only let fido_alloc = UsbBusAllocator::new(usb_fido_dev); let mut fido_class = UsbHidClassBuilder::new() - .add_interface( - RawFidoInterface::default_config() + .add_device( + RawFidoConfig::default() ) .build(&fido_alloc); @@ -553,12 +534,12 @@ pub(crate) fn main_hw() -> ! { let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; let mut u2f_ipc = buffer.to_original::().unwrap(); if fido_listener_pid == msg.sender.pid() { - let mut u2f_msg = RawFidoMsg::default(); + let mut u2f_msg = RawFidoReport::default(); assert_eq!(u2f_ipc.code, U2fCode::Tx, "Expected U2fCode::Tx in wrapper"); u2f_msg.packet.copy_from_slice(&u2f_ipc.data); let u2f = match view { - Views::FidoWithKbd => composite.interface::, _>(), - Views::FidoOnly => fido_class.interface::, _>(), + Views::FidoWithKbd => composite.device::, _>(), + Views::FidoOnly => fido_class.device::, _>(), #[cfg(feature="mass-storage")] Views::MassStorage => panic!("did not expect u2f tx when in mass storage mode!"), Views::Serial => panic!("did not expect u2f tx while in serial mode!"), @@ -575,22 +556,21 @@ pub(crate) fn main_hw() -> ! { let maybe_u2f = match view { Views::FidoWithKbd => { if usb_dev.poll(&mut [&mut composite]) { - let keyboard = composite.interface::, _>(); - match keyboard.read_report() { + match composite.device::, _>().read_report() { Ok(l) => { log::info!("keyboard LEDs: {:?}", l); led_state = l; } Err(e) => log::trace!("KEYB ERR: {:?}", e), } - Some(composite.interface::, _>()) + Some(composite.device::, _>()) } else { None } } Views::FidoOnly => { if fido_dev.poll(&mut [&mut fido_class]) { - Some(fido_class.interface::, _>()) + Some(fido_class.device::, _>()) } else { None } @@ -796,8 +776,8 @@ pub(crate) fn main_hw() -> ! { usbmgmt.ll_reset(false); } } - let keyboard = composite.interface::, _>(); - keyboard.write_report(&[]).ok(); // queues an "all key-up" for the interface + let keyboard = composite.device::, _>(); + keyboard.write_report([Keyboard::NoEventIndicated]).ok(); // queues an "all key-up" for the interface keyboard.tick().ok(); } UsbDeviceType::Fido => { @@ -894,8 +874,8 @@ pub(crate) fn main_hw() -> ! { // type matches, do nothing } } - let keyboard = composite.interface::, _>(); - keyboard.write_report(&[]).ok(); // queues an "all key-up" for the interface + let keyboard = composite.device::, _>(); + keyboard.write_report([Keyboard::NoEventIndicated]).ok(); // queues an "all key-up" for the interface keyboard.tick().ok(); } UsbDeviceType::Fido => { @@ -1026,21 +1006,36 @@ pub(crate) fn main_hw() -> ! { if usb_dev.state() == UsbDeviceState::Configured { let mut codes = Vec::::new(); if code0 != 0 { - codes.push(Keyboard::from_primitive(code0 as u8)); + codes.push( + match native_map { + KeyMap::Dvorak => mappings::char_to_hid_code_dvorak(code0 as u8 as char)[0], + _ => mappings::char_to_hid_code_us101(code0 as u8 as char)[0], + } + ); } if code1 != 0 { - codes.push(Keyboard::from_primitive(code1 as u8)); + codes.push( + match native_map { + KeyMap::Dvorak => mappings::char_to_hid_code_dvorak(code1 as u8 as char)[0], + _ => mappings::char_to_hid_code_us101(code1 as u8 as char)[0], + } + ); } if code2 != 0 { - codes.push(Keyboard::from_primitive(code2 as u8)); + codes.push( + match native_map { + KeyMap::Dvorak => mappings::char_to_hid_code_dvorak(code2 as u8 as char)[0], + _ => mappings::char_to_hid_code_us101(code2 as u8 as char)[0], + } + ); } let auto_up = if autoup == 1 {true} else {false}; - let keyboard = composite.interface::, _>(); - keyboard.write_report(&codes).ok(); + let keyboard = composite.device::, _>(); + keyboard.write_report(codes).ok(); keyboard.tick().ok(); tt.sleep_ms(autotype_delay_ms).ok(); if auto_up { - keyboard.write_report(&[]).ok(); // this is the key-up + keyboard.write_report([Keyboard::NoEventIndicated]).ok(); // this is the key-up keyboard.tick().ok(); tt.sleep_ms(autotype_delay_ms).ok(); } @@ -1111,11 +1106,11 @@ pub(crate) fn main_hw() -> ! { KeyMap::Dvorak => mappings::char_to_hid_code_dvorak(ch), _ => mappings::char_to_hid_code_us101(ch), }; - let keyboard = composite.interface::, _>(); - keyboard.write_report(&codes).ok(); + let keyboard = composite.device::, _>(); + keyboard.write_report(codes).ok(); keyboard.tick().ok(); tt.sleep_ms(autotype_delay_ms).ok(); - keyboard.write_report(&[]).ok(); // this is the key-up + keyboard.write_report([Keyboard::NoEventIndicated]).ok(); // this is the key-up keyboard.tick().ok(); tt.sleep_ms(autotype_delay_ms).ok(); sent += 1; diff --git a/services/usb-device-xous/src/mappings.rs b/services/usb-device-xous/src/mappings.rs index c6706ac26..c68b9a18b 100644 --- a/services/usb-device-xous/src/mappings.rs +++ b/services/usb-device-xous/src/mappings.rs @@ -1,5 +1,5 @@ #![allow(dead_code)] -pub use usbd_human_interface_device::page::Keyboard as UsbKeyCode; +pub use xous_usb_hid::page::Keyboard as UsbKeyCode; #[cfg(any(feature="precursor", feature="renode"))] pub fn char_to_hid_code_us101(key: char) -> Vec { diff --git a/services/usb-test/Cargo.toml b/services/usb-test/Cargo.toml index d92ec2a44..af4f86dbc 100644 --- a/services/usb-test/Cargo.toml +++ b/services/usb-test/Cargo.toml @@ -7,20 +7,20 @@ description = "USB test & development stub" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" -log-server = { package = "xous-api-log", version = "0.1.49" } -ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.49" } -xous-names = { package = "xous-api-names", version = "0.9.51" } +xous = "0.9.55" +log-server = { package = "xous-api-log", version = "0.1.51" } +ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.51" } +xous-names = { package = "xous-api-names", version = "0.9.53" } log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} -susres = {package = "xous-api-susres", version = "0.9.49"} +susres = {package = "xous-api-susres", version = "0.9.51"} keyboard = {path = "../keyboard"} bitfield = "0.13.2" vcell = "0.1.3" utralib = { version = "0.1.23", optional = true, default-features = false } -usbd-human-interface-device = "0.1.1" +xous-usb-hid = {git = "https://github.com/betrusted-io/xous-usb-hid.git"} embedded-time = "0.12.1" # required by the keyboard interface [dependencies.usb-device] diff --git a/services/usb-test/src/main.rs b/services/usb-test/src/main.rs index e6a3c456c..9a218d64c 100644 --- a/services/usb-test/src/main.rs +++ b/services/usb-test/src/main.rs @@ -27,9 +27,9 @@ use std::collections::BTreeMap; use usb_device::prelude::*; use usb_device::class_prelude::*; -use usbd_human_interface_device::page::Keyboard; -use usbd_human_interface_device::device::keyboard::NKROBootKeyboardInterface; -use usbd_human_interface_device::prelude::*; +use xous_usb_hid::page::Keyboard; +use xous_usb_hid::device::keyboard::NKROBootKeyboard; +use xous_usb_hid::prelude::*; use embedded_time::Clock; use std::convert::TryInto; @@ -95,12 +95,12 @@ fn main() -> ! { let usb_alloc = UsbBusAllocator::new(usbdev); let clock = EmbeddedClock::new(); let mut keyboard = UsbHidClassBuilder::new() - .add_interface( - NKROBootKeyboardInterface::default_config(&clock), + .add_device( + NKROBootKeyboard::default(&clock), ) .build(&usb_alloc); let mut usb_dev = UsbDeviceBuilder::new(&usb_alloc, UsbVidPid(0x1209, 0x3613)) - .manufacturer("usbd-human-interface-device") + .manufacturer("xous-usb-hid") .product("NKRO Keyboard") .serial_number("PRECURSOR") .build(); diff --git a/services/xous-log/Cargo.toml b/services/xous-log/Cargo.toml index 35958d70e..3e56f424d 100644 --- a/services/xous-log/Cargo.toml +++ b/services/xous-log/Cargo.toml @@ -10,9 +10,9 @@ homepage = "https://betrusted.io/xous-book/" # Dependency versions enforced by Cargo.lock. [dependencies] -xous-api-log = {package = "xous-api-log", version = "0.1.49"} -xous = "0.9.53" -xous-ipc = "0.9.53" +xous-api-log = {package = "xous-api-log", version = "0.1.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} @@ -24,7 +24,7 @@ rkyv = {version = "0.4.3", default-features = false, features = ["const_generics [target.'cfg(target_arch = "arm")'.dependencies] atsama5d27 = { git = "https://github.com/Foundation-Devices/atsama5d27.git", branch = "master", features = ["lcd-console"]} -xous = { version = "0.9.53", features = ["v2p"] } # v2p feature is used when lcd-console feature is turned on +xous = { version = "0.9.55", features = ["v2p"] } # v2p feature is used when lcd-console feature is turned on [features] cramium-soc = ["utralib/cramium-soc"] diff --git a/services/xous-names/Cargo.toml b/services/xous-names/Cargo.toml index 185618dd7..643d28533 100644 --- a/services/xous-names/Cargo.toml +++ b/services/xous-names/Cargo.toml @@ -10,10 +10,10 @@ homepage = "https://betrusted.io/" # Dependency versions enforced by Cargo.lock. [dependencies] -xous-api-names = "0.9.51" -log-server = {package = "xous-api-log", version = "0.1.49"} -xous = "0.9.53" -xous-ipc = "0.9.53" +xous-api-names = "0.9.53" +log-server = {package = "xous-api-log", version = "0.1.51"} +xous = "0.9.55" +xous-ipc = "0.9.55" num-derive = {version = "0.3.3", default-features = false} num-traits = {version = "0.2.14", default-features = false} log = "0.4.14" diff --git a/services/xous-susres/Cargo.toml b/services/xous-susres/Cargo.toml index ac0d2e6e4..310dde805 100644 --- a/services/xous-susres/Cargo.toml +++ b/services/xous-susres/Cargo.toml @@ -10,11 +10,11 @@ homepage = "https://betrusted.io/xous-book/" # Dependency versions enforced by Cargo.lock. [dependencies] -xous-api-susres = "0.9.49" -xous-names = { package = "xous-api-names", version = "0.9.51" } -log-server = { package = "xous-api-log", version = "0.1.49" } -xous = "0.9.53" -xous-ipc = "0.9.53" +xous-api-susres = "0.9.51" +xous-names = { package = "xous-api-names", version = "0.9.53" } +log-server = { package = "xous-api-log", version = "0.1.51" } +xous = "0.9.55" +xous-ipc = "0.9.55" log = "0.4.14" num-derive = {version = "0.3.3", default-features = false} diff --git a/services/xous-ticktimer/Cargo.toml b/services/xous-ticktimer/Cargo.toml index 2ffbcdc69..51d70a5b2 100644 --- a/services/xous-ticktimer/Cargo.toml +++ b/services/xous-ticktimer/Cargo.toml @@ -10,12 +10,12 @@ homepage = "https://betrusted.io/xous-book/" # Dependency versions enforced by Cargo.lock. [dependencies] -xous-api-ticktimer = "0.9.49" -xous = "0.9.53" -xous-ipc = "0.9.53" -xous-names = {package = "xous-api-names", version = "0.9.51"} -log-server = {package = "xous-api-log", version = "0.1.49"} -susres = {package = "xous-api-susres", version = "0.9.49"} +xous-api-ticktimer = "0.9.51" +xous = "0.9.55" +xous-ipc = "0.9.55" +xous-names = {package = "xous-api-names", version = "0.9.53"} +log-server = {package = "xous-api-log", version = "0.1.51"} +susres = {package = "xous-api-susres", version = "0.9.51"} log = "0.4.14" rkyv = {version = "0.4.3", default-features = false, features = ["const_generics"]} num-derive = {version = "0.3.3", default-features = false} diff --git a/tools/app_server.py b/tools/app_server.py new file mode 100644 index 000000000..965c771bb --- /dev/null +++ b/tools/app_server.py @@ -0,0 +1,70 @@ +#! /usr/bin/env python3 + +import argparse +import json +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import unquote + +class XousAppServer(BaseHTTPRequestHandler): + def __init__(self, profile, target, context_to_app, context_to_menus, *args): + self.profile = profile + self.target = target + self.context_to_app = context_to_app + self.context_to_menus = context_to_menus + BaseHTTPRequestHandler.__init__(self, *args) + + def do_GET(self): + # an empty path should return a list of apps + path = self.path.split('/')[-1] + if path == '': + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + + l = json.dumps(list(self.context_to_menus.items())) + + self.wfile.write(l.encode()) + else: + name = self.context_to_app[unquote(path)] + with open('target/{}/{}/{}'.format(self.target, self.profile, name), 'rb') as f: + data = f.read() + self.send_response(200) + self.send_header('Content-Type', 'application/octet-stream') + self.send_header('Content-Length', str(len(data))) + self.end_headers() + + self.wfile.write(data) + +def main(): + parser = argparse.ArgumentParser(description="Xous App Server") + parser.add_argument('apps', metavar='APP', nargs='+', + help='Specifies which apps to serve') + parser.add_argument('-p', '--port', + help='Specifies the port that the server runs on', + required=True, type=int) + parser.add_argument('--profile', default="release", + help="Either debug or release depending on how the apps were compiled. Defaults to release") + parser.add_argument('--target', default="riscv32imac-unknown-xous-elf", + help="The target that the apps were compile to. Defaults to riscv32imac-unknown-xous-elf") + args = parser.parse_args() + + # get the GAM names for each of the apps + with open('apps/manifest.json', 'r') as f: + manifest = json.loads(f.read()) + + context_to_app = {} + context_to_menus = {} + + for app in args.apps: + context_name = manifest[app]['context_name'] + context_to_app[context_name] = app + if 'submenu' in manifest[app]: + context_to_menus[context_name] = manifest[app]['submenu'] + else: + context_to_menus[context_name] = 0 + + server = HTTPServer(('0.0.0.0', args.port), lambda *server_args: XousAppServer(args.profile, args.target, context_to_app, context_to_menus, *server_args)) + server.serve_forever() + +if __name__ == "__main__": + main() diff --git a/tools/perflib/Cargo.toml b/tools/perflib/Cargo.toml index 299890ac1..ffd8f1351 100644 --- a/tools/perflib/Cargo.toml +++ b/tools/perflib/Cargo.toml @@ -7,7 +7,7 @@ description = "Performance counter tooling" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" +xous = "0.9.55" log = "0.4.14" utralib = { version = "0.1.23", default-features = false } diff --git a/xous-ipc/Cargo.toml b/xous-ipc/Cargo.toml index cf003a384..efb08b698 100644 --- a/xous-ipc/Cargo.toml +++ b/xous-ipc/Cargo.toml @@ -4,12 +4,12 @@ description = "System call interface for Xous" edition = "2018" license = "MIT OR Apache-2.0" name = "xous-ipc" -version = "0.9.53" +version = "0.9.55" repository = "https://github.com/betrusted-io/xous-core/" homepage = "https://betrusted.io/" # Dependency versions enforced by Cargo.lock. [dependencies] -xous = "0.9.53" +xous = "0.9.55" bitflags = {version = "1"} rkyv = {version = "0.4.3", features = ["const_generics"], default-features = false} diff --git a/xous-rs/Cargo.toml b/xous-rs/Cargo.toml index 3d607b017..0f65df709 100644 --- a/xous-rs/Cargo.toml +++ b/xous-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xous" -version = "0.9.53" +version = "0.9.55" authors = ["Sean Cross "] edition = "2021" license = "MIT OR Apache-2.0" diff --git a/xous-rs/build.rs b/xous-rs/build.rs deleted file mode 100644 index c2bce4fe2..000000000 --- a/xous-rs/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=build.rs"); -} diff --git a/xous-rs/src/arch/hosted/mod.rs b/xous-rs/src/arch/hosted/mod.rs index ec20370ef..7fedcf26e 100644 --- a/xous-rs/src/arch/hosted/mod.rs +++ b/xous-rs/src/arch/hosted/mod.rs @@ -49,7 +49,7 @@ lazy_static::lazy_static! { // Note: &* is required due to how `lazy_static` works behind the scenes: // https://github.com/rust-lang-nursery/lazy-static.rs/issues/119#issuecomment-419595818 - let mut conn = TcpStream::connect(&*NETWORK_CONNECT_ADDRESS).expect("unable to connect to Xous kernel"); + let mut conn = TcpStream::connect(*NETWORK_CONNECT_ADDRESS).expect("unable to connect to Xous kernel"); // Disable Nagel's algorithm, since we're running locally and managing buffers ourselves conn.set_nodelay(true).unwrap(); @@ -262,7 +262,7 @@ fn read_next_syscall_result( } else if kind == CallMemoryKind::Borrow || kind == CallMemoryKind::MutableBorrow { // Read the buffer back from the remote host. use core::slice; - let mut data = unsafe { slice::from_raw_parts_mut(mem.as_mut_ptr(), mem.len()) }; + let data = unsafe { slice::from_raw_parts_mut(mem.as_mut_ptr(), mem.len()) }; // If it's a Borrow, verify the contents haven't changed by saving the previous // buffer in a Vec called `previous_data`. @@ -272,7 +272,7 @@ fn read_next_syscall_result( }; // Read the incoming data from the network. - if let Err(e) = stream.read_exact(&mut data) { + if let Err(e) = stream.read_exact(data) { eprintln!("Server shut down: {}", e); std::process::exit(0); } diff --git a/xous-rs/src/arch/hosted/process.rs b/xous-rs/src/arch/hosted/process.rs index a6ff8b252..ca9f5d99b 100644 --- a/xous-rs/src/arch/hosted/process.rs +++ b/xous-rs/src/arch/hosted/process.rs @@ -71,7 +71,7 @@ impl TryFrom<[usize; 7]> for ProcessInit { type Error = crate::Error; fn try_from(src: [usize; 7]) -> core::result::Result { let mut exploded = vec![]; - for word in src[0..4].into_iter() { + for word in src[0..4].iter() { exploded.extend_from_slice(&(*word as u32).to_le_bytes()); } let mut key = [0u8; 16]; @@ -156,7 +156,7 @@ pub fn create_process_post( // println!("Launching process..."); Command::new(shell) - .args(&args) + .args(args) .env("XOUS_SERVER", server_env) .env("XOUS_PID", pid_env) .env("XOUS_PROCESS_NAME", process_name_env) diff --git a/xous-rs/src/definitions.rs b/xous-rs/src/definitions.rs index d7da743c5..4165a78d6 100644 --- a/xous-rs/src/definitions.rs +++ b/xous-rs/src/definitions.rs @@ -52,8 +52,8 @@ pub const BASE_QUANTA_MS: u32 = 10; // where _|TT|_ and _|TE|_ are bookends around the data to be reported // is a single-word identifier that routes the data to a given parser // is free-form data, which will be split at comma boundaries by the parser -pub const BOOKEND_START: &'static str = "_|TT|_"; -pub const BOOKEND_END: &'static str = "_|TE|_"; +pub const BOOKEND_START: &str = "_|TT|_"; +pub const BOOKEND_END: &str = "_|TE|_"; #[cfg(not(target_os = "xous"))] use core::sync::atomic::AtomicU64; @@ -425,7 +425,7 @@ impl Result { 9, me_enc[0], me_enc[1], me_enc[2], me_enc[3], me_enc[4], me_enc[5], me_enc[6], ] } - Result::ThreadID(ctx) => [10, *ctx as usize, 0, 0, 0, 0, 0, 0], + Result::ThreadID(ctx) => [10, *ctx, 0, 0, 0, 0, 0, 0], Result::ProcessID(pid) => [11, pid.get() as _, 0, 0, 0, 0, 0, 0], Result::Unimplemented => [21, 0, 0, 0, 0, 0, 0, 0], Result::BlockedProcess => [13, 0, 0, 0, 0, 0, 0, 0], diff --git a/xous-rs/src/definitions/messages/envelope.rs b/xous-rs/src/definitions/messages/envelope.rs index cc9ba3fcf..d4c5fe766 100644 --- a/xous-rs/src/definitions/messages/envelope.rs +++ b/xous-rs/src/definitions/messages/envelope.rs @@ -91,10 +91,10 @@ impl Envelope { return Err((ManuallyDrop::into_inner(manual_self), e)); } - return Err(( + Err(( ManuallyDrop::into_inner(manual_self), crate::Error::MemoryInUse, - )); + )) } Message::BlockingScalar(_) => { let result = crate::send_message(connection, body); @@ -113,10 +113,10 @@ impl Envelope { } return Ok(()); } - return Err(( + Err(( ManuallyDrop::into_inner(manual_self), crate::Error::MemoryInUse, - )); + )) } Message::Borrow(_) | Message::MutableBorrow(_) | Message::Scalar(_) => { diff --git a/xous-rs/src/definitions/messages/mod.rs b/xous-rs/src/definitions/messages/mod.rs index 5b93bf45a..60a127a44 100644 --- a/xous-rs/src/definitions/messages/mod.rs +++ b/xous-rs/src/definitions/messages/mod.rs @@ -276,7 +276,7 @@ impl Message { } pub fn to_usize(&self) -> [usize; 6] { - let ret = match &*self { + let ret = match self { Message::MutableBorrow(m) => (0, m.to_usize()), Message::Borrow(m) => (1, m.to_usize()), Message::Move(m) => (2, m.to_usize()), diff --git a/xous-rs/src/syscall.rs b/xous-rs/src/syscall.rs index 13d3f03e2..576bc7348 100644 --- a/xous-rs/src/syscall.rs +++ b/xous-rs/src/syscall.rs @@ -728,7 +728,7 @@ impl SysCall { SysCall::ReturnToParent(a1, a2) => [ SysCallNumber::ReturnToParent as usize, a1.get() as usize, - *a2 as usize, + *a2, 0, 0, 0, @@ -751,7 +751,7 @@ impl SysCall { SysCall::SwitchTo(a1, a2) => [ SysCallNumber::SwitchTo as usize, a1.get() as usize, - *a2 as usize, + *a2, 0, 0, 0, @@ -770,7 +770,7 @@ impl SysCall { ], SysCall::IncreaseHeap(a1, a2) => [ SysCallNumber::IncreaseHeap as usize, - *a1 as usize, + *a1, a2.bits(), 0, 0, @@ -780,7 +780,7 @@ impl SysCall { ], SysCall::DecreaseHeap(a1) => [ SysCallNumber::DecreaseHeap as usize, - *a1 as usize, + *a1, 0, 0, 0, @@ -841,17 +841,17 @@ impl SysCall { SysCallNumber::SendMessage as usize, *a1 as usize, a2.message_type(), - mm.id as usize, + mm.id, mm.buf.as_ptr() as usize, mm.buf.len(), - mm.offset.map(|x| x.get()).unwrap_or(0) as usize, - mm.valid.map(|x| x.get()).unwrap_or(0) as usize, + mm.offset.map(|x| x.get()).unwrap_or(0), + mm.valid.map(|x| x.get()).unwrap_or(0), ], Message::Scalar(sc) | Message::BlockingScalar(sc) => [ SysCallNumber::SendMessage as usize, *a1 as usize, a2.message_type(), - sc.id as usize, + sc.id, sc.arg1, sc.arg2, sc.arg3, @@ -914,17 +914,17 @@ impl SysCall { SysCallNumber::TrySendMessage as usize, *a1 as usize, a2.message_type(), - mm.id as usize, + mm.id, mm.buf.as_ptr() as usize, mm.buf.len(), - mm.offset.map(|x| x.get()).unwrap_or(0) as usize, - mm.valid.map(|x| x.get()).unwrap_or(0) as usize, + mm.offset.map(|x| x.get()).unwrap_or(0), + mm.valid.map(|x| x.get()).unwrap_or(0), ], Message::Scalar(sc) | Message::BlockingScalar(sc) => [ SysCallNumber::TrySendMessage as usize, *a1 as usize, a2.message_type(), - sc.id as usize, + sc.id, sc.arg1, sc.arg2, sc.arg3, @@ -978,7 +978,7 @@ impl SysCall { ], SysCall::JoinThread(tid) => [ SysCallNumber::JoinThread as usize, - *tid as usize, + *tid, 0, 0, 0, @@ -1073,13 +1073,13 @@ impl SysCall { MemoryAddress::new(a3), ), SysCallNumber::FreeInterrupt => SysCall::FreeInterrupt(a1), - SysCallNumber::SwitchTo => SysCall::SwitchTo(pid_from_usize(a1)?, a2 as usize), + SysCallNumber::SwitchTo => SysCall::SwitchTo(pid_from_usize(a1)?, a2), SysCallNumber::ReadyThreads => SysCall::ReadyThreads(pid_from_usize(a1)?), SysCallNumber::IncreaseHeap => SysCall::IncreaseHeap( - a1 as usize, + a1, crate::MemoryFlags::from_bits(a2).ok_or(Error::InvalidSyscall)?, ), - SysCallNumber::DecreaseHeap => SysCall::DecreaseHeap(a1 as usize), + SysCallNumber::DecreaseHeap => SysCall::DecreaseHeap(a1), SysCallNumber::UpdateMemoryFlags => SysCall::UpdateMemoryFlags( unsafe { MemoryRange::new(a1, a2) }?, crate::MemoryFlags::from_bits(a3).ok_or(Error::InvalidSyscall)?, @@ -1986,7 +1986,7 @@ pub fn reply_and_receive_next( /// ## Arguments /// /// * **server**: The SID of the server to receive messages from -/// * **msg**: An Option specifying the message to return. +/// * **msg**: An `Option` specifying the message to return. /// * **return_type**: If 1 or 2, responds to a BlockingScalarMessage /// with a Scalar1 or a Scalar2. Otherwise, will respond /// as normal. diff --git a/xtask/src/app_manifest.rs b/xtask/src/app_manifest.rs index 076f47df7..77a768cf4 100644 --- a/xtask/src/app_manifest.rs +++ b/xtask/src/app_manifest.rs @@ -67,7 +67,7 @@ pub(crate) fn generate_app_menus(apps: &Vec) { writeln!( gam_tokens, "pub const APP_NAME_{}: &'static str = \"{}\";", - app_name.to_uppercase(), + app_name.to_uppercase().replace("-", "_"), manifest.context_name, ) .unwrap(); @@ -77,7 +77,7 @@ pub(crate) fn generate_app_menus(apps: &Vec) { gam_tokens, "pub const APP_MENU_{}_{}: &'static str = \"{} Submenu {}\";", i, - app_name.to_uppercase(), + app_name.to_uppercase().replace("-", "_"), manifest.context_name, i, ) @@ -89,16 +89,16 @@ pub(crate) fn generate_app_menus(apps: &Vec) { gam_tokens, "\npub const EXPECTED_APP_CONTEXTS: &[&'static str] = &[" ) - .unwrap(); + .unwrap(); for (app_name, manifest) in working_set.iter() { - writeln!(gam_tokens, " APP_NAME_{},", app_name.to_uppercase(),).unwrap(); + writeln!(gam_tokens, " APP_NAME_{},", app_name.to_uppercase().replace("-", "_"),).unwrap(); if let Some(menu_count) = manifest.submenu { for i in 0..menu_count { writeln!( gam_tokens, " APP_MENU_{}_{},", i, - app_name.to_uppercase(), + app_name.to_uppercase().replace("-", "_"), ) .unwrap(); } @@ -151,7 +151,7 @@ pub(crate) fn app_dispatch(gam: &gam::Gam, token: [u32; 4], index: usize) -> Res Ok(()) }},", index, - app_name.to_uppercase() + app_name.to_uppercase().replace("-", "_") ) .unwrap(); } diff --git a/xtask/src/builder.rs b/xtask/src/builder.rs index d517f205f..60b7e6caa 100644 --- a/xtask/src/builder.rs +++ b/xtask/src/builder.rs @@ -130,6 +130,8 @@ pub(crate) struct Builder { locale_stash: String, /// when set to true, hosted mode builds but does not run dry_run: bool, + /// when set to true, user selected packages are compiled but no image is created + no_image: bool, } impl Builder { @@ -154,6 +156,7 @@ impl Builder { locale_override: None, locale_stash: String::new(), dry_run: false, + no_image: false, } } /// Specify an alternate loader key, as a String that can encode a file name @@ -237,6 +240,14 @@ impl Builder { self.kernel = CrateSpec::Local("xous-kernel".to_string(), false); self } + pub fn target_precursor_no_image<'a>(&'a mut self, soc_version: &str) -> &'a mut Builder { + self.target = Some(crate::TARGET_TRIPLE_RISCV32.to_string()); + self.stream = BuildStream::Release; + self.utra_target = format!("precursor-{}", soc_version).to_string(); + self.run_svd2repl = false; + self.no_image = true; + self + } /// Configure for ARM targets pub fn target_arm(&mut self) -> &mut Builder { @@ -597,8 +608,13 @@ impl Builder { false )?; - // ------ either create an image, or launch hosted mode ------ - if self.target.is_none() { // hosted mode doesn't specify a cross-compilation target! + // ------ either stop here, create an image, or launch hosted mode ------ + if self.no_image { + println!("The following apps/services were compiled:"); + for path in services_path { + println!("{}", path); + } + } else if self.target.is_none() { // hosted mode doesn't specify a cross-compilation target! // throw a warning if prebuilts are specified for hosted mode for item in [&self.services[..], &self.apps[..]].concat() { match item { diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 16470c1df..b8a0bbb4e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -251,7 +251,7 @@ fn main() -> Result<(), Box> { builder.target_hosted() .add_services(&gfx_base_pkgs.into_iter().map(String::from).collect()) .add_services(&get_cratespecs()) - .add_feature("graphics-server/testing"); + .add_feature("graphics-server/gfx-testing"); }, Some("hosted-ci") => { builder.target_hosted() @@ -363,6 +363,10 @@ fn main() -> Result<(), Box> { .add_services(&user_pkgs.into_iter().map(String::from).collect()) .add_feature("avalanchetest"); } + Some("compile-apps") => { + builder.target_precursor_no_image(PRECURSOR_SOC_VERSION) + .add_services(&gfx_base_pkgs.into_iter().map(String::from).collect()); + } // ------ Cramium hardware image configs ------ Some("cramium-fpga") | Some("cramium-soc") => { @@ -502,6 +506,7 @@ Other commands: generate-locales (re)generate the locales include for the language selected in locales/src/locale.rs wycheproof-import generate binary test vectors for engine-25519 from whycheproof-import/x25519.json install-toolkit installs Xous toolkit with no prompt, useful in CI. Specify `--force` to remove existing toolchains + compile-apps Just compiles the apps specified in [cratespecs], for example in order to use app server Note: By default, the `ticktimer` will get rebuilt every time. You can skip this by appending `--no-timestamp` to the command. " diff --git a/xtask/src/verifier.rs b/xtask/src/verifier.rs index 810f02b38..8df4faffd 100644 --- a/xtask/src/verifier.rs +++ b/xtask/src/verifier.rs @@ -12,12 +12,12 @@ pub fn check_project_consistency() -> Result<(), DynError> { // TODO: retire utralib/svd2utra from publication as well let check_pkgs = [ // this set updates with kernel API changes - "xous@0.9.53", - "xous-ipc@0.9.53", - "xous-api-log@0.1.49", - "xous-api-names@0.9.51", - "xous-api-susres@0.9.49", - "xous-api-ticktimer@0.9.49", + "xous@0.9.55", + "xous-ipc@0.9.55", + "xous-api-log@0.1.51", + "xous-api-names@0.9.53", + "xous-api-susres@0.9.51", + "xous-api-ticktimer@0.9.51", ]; // utra/svd2utra changes are downgraded to warnings because these now prefer to pull // from the local patch version, so any inconsistency simply indicates we forgot to From 270746daf4dcc391f5683439ba5570dac7f0590d Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 7 Dec 2023 18:10:06 +1100 Subject: [PATCH 02/49] update sigchat readme --- apps/sigchat/README.md | 2 +- apps/sigchat/src/manager/README.md | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 apps/sigchat/src/manager/README.md diff --git a/apps/sigchat/README.md b/apps/sigchat/README.md index 41f2d0e52..d7e3f2f3b 100644 --- a/apps/sigchat/README.md +++ b/apps/sigchat/README.md @@ -1,4 +1,4 @@ -# mtxchat +# sigchat This is the skeleton of a Signal chat application. diff --git a/apps/sigchat/src/manager/README.md b/apps/sigchat/src/manager/README.md new file mode 100644 index 000000000..1442edf31 --- /dev/null +++ b/apps/sigchat/src/manager/README.md @@ -0,0 +1,9 @@ +The structure of the Signal interface is modelled on [signal-cli](https://github.com/AsamK/signal-cli) by AsamK and contributors. signal-cli is a command-line interface for libsignal-service-java. It supports registering, verifying, sending and receiving messages. This development provides a welcome road-map of the various common transactions between a Signal client and server - see the signal-cli [Manual Page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc). This road-map is sketched out in apps/sigchat/src/manager.rs, but will be subject to refinement as sigchat development progresses. + +Manager::methods() will likely make calls on the official [libsignal](https://github.com/signalapp/libsignal#overview) library. + +> libsignal contains platform-agnostic APIs used by the official Signal clients and servers, exposed as a Java, Swift, or TypeScript library. The underlying implementations are written in Rust. + +> This repository is used by the Signal client apps ([Android](https://github.com/signalapp/Signal-Android), [iOS](https://github.com/signalapp/Signal-iOS), and [Desktop](https://github.com/signalapp/Signal-Desktop)) as well as server-side. Use outside of Signal is unsupported. + +libsignal is released under the AGPLv3 licence, and therefore not directly compatible with xous released under the MIT license. Therefore, libsignal must be wrapped in a suitable API, and included as a binary in the hardware image. From 404dc29fecf5d443144fb47d8b0cfbc05e09340f Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 7 Dec 2023 18:13:47 +1100 Subject: [PATCH 03/49] include Signal registered Number in Account::read() --- apps/sigchat/src/account.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/sigchat/src/account.rs b/apps/sigchat/src/account.rs index eacabb8f0..b8fc5f16b 100644 --- a/apps/sigchat/src/account.rs +++ b/apps/sigchat/src/account.rs @@ -38,17 +38,19 @@ impl Account { match ( get(&pddb, pddb_dict, SERVICE_ENVIRONMENT_KEY), get(&pddb, pddb_dict, REGISTERED_KEY), + get(&pddb, pddb_dict, NUMBER_KEY), ) { - (Ok(Some(service_environment)), Ok(Some(registered))) => Ok(Account { + (Ok(Some(service_environment)), Ok(Some(registered)), Ok(number)) => Ok(Account { pddb: pddb, pddb_dict: pddb_dict.to_string(), service_environment: ServiceEnvironment::from_str(&service_environment).unwrap(), - number: None, + number: number, registered: registered.parse().unwrap(), }), - (Err(e), _) => Err(e), - (_, Err(e)) => Err(e), - (_, _) => Err(Error::from(ErrorKind::InvalidData)), + (Err(e), _, _) => Err(e), + (_, Err(e), _) => Err(e), + (_, _, Err(e)) => Err(e), + (_, _, _) => Err(Error::from(ErrorKind::InvalidData)), } } From b032a80a1898a85acc322531b02f89e218ff9991 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 7 Dec 2023 18:29:50 +1100 Subject: [PATCH 04/49] refine Sigchat::connect() to link/register on first use --- apps/sigchat/locales/i18n.json | 8 +++++ apps/sigchat/src/lib.rs | 55 ++++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/apps/sigchat/locales/i18n.json b/apps/sigchat/locales/i18n.json index 638ec62f4..cf87e40ab 100644 --- a/apps/sigchat/locales/i18n.json +++ b/apps/sigchat/locales/i18n.json @@ -7,6 +7,14 @@ "en": "Link to an existing Signal Account", "en-tts": "Link to an existing Signal Account" }, + "sigchat.account.link.name": { + "en": "Please provide a name for this device", + "en-tts": "Please provide a name for this device" + }, + "sigchat.account.link.scan": { + "en": "Please scan with primary Signal device", + "en-tts": "Please scan with primary Signal device" + }, "sigchat.account.register": { "en": "Register a new Signal Account", "en-tts": "Register a new Signal Account" diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index 4b2414c6f..0a29b9e80 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -47,31 +47,33 @@ impl<'a> SigChat<'a> { /// Connect to the Signal servers /// - pub fn connect(&mut self) -> bool { + pub fn connect(&mut self) -> Result { log::info!("Attempting connect to Signal server"); if self.wifi() { if self.manager.is_none() { - self.account_setup(); + log::info!("Setting up Signal Account Manager"); + let account = match Account::read(SIGCHAT_ACCOUNT) { + Ok(account) => account, + Err(e) => self.account_setup()?, + }; + self.manager = Some(Manager::new(account, TrustMode::OnFirstUse)); + } + if let Some(manager) = &self.manager { + log::info!("Signal Account Manager OK"); } - if let Some(_manager) = &self.manager { - log::info!("Signal manager OK" ); - } else { - self.modals - .show_notification(t!("sigchat.account.failed", locales::LANG), None) - .expect("notification failed"); - }; } else { self.modals .show_notification(t!("sigchat.wifi.warning", locales::LANG), None) .expect("notification failed"); } self.dialogue_set(None); - false + Ok(false) } /// Setup a Signal Account via Registration or Linking /// - fn account_setup(&mut self) { + fn account_setup(&mut self) -> Result { + log::info!("Attempting to setup a Signal Account"); self.modals .add_list_item(t!("sigchat.account.link", locales::LANG)) .expect("failed add list item"); @@ -83,11 +85,18 @@ impl<'a> SigChat<'a> { .expect("failed radiobutton modal"); match self.modals.get_radio_index() { Ok(index) => match index { - 0 => self.account_link(), - 1 => self.account_register(), - _ => log::warn!("invalid index"), + 0 => Ok(self.account_link()?), + 1 => Ok(self.account_register()?), + + _ => { + log::warn!("invalid index"); + Err(Error::new(ErrorKind::Other, "invalid radio index")) + } }, - Err(e) => log::warn!("failed to select register/link {:?}", e), + Err(e) => { + log::warn!("failed to present account setup radio buttons: {:?}", e); + Err(Error::new(ErrorKind::Other, "failed to present account setup radio buttons")) + } } } @@ -108,7 +117,8 @@ impl<'a> SigChat<'a> { /// The phone number must include the country calling code, i.e. the number must start with a "+" sign. /// Warning: this will disable the authentication of your phone as a primary device. /// - pub fn account_register(&mut self) { + pub fn account_register(&mut self) -> Result { + log::info!("Attempting to Register a new Signal Account"); self.modals .show_notification(&"sorry - registration is not implemented yet", None) .expect("notification failed"); @@ -116,19 +126,18 @@ impl<'a> SigChat<'a> { Ok(number) => { log::info!("registration phone number = {:?}", number); match Account::new(SIGCHAT_ACCOUNT) { - Ok(mut account) => { - match account.set_number(&number.to_string()) { - Ok(_number) => { - self.manager = Some(Manager::new(account, TrustMode::OnFirstUse)); - } - Err(e) => log::warn!("failed to set Account number: {e}"), + Ok(mut account) => match account.set_number(&number.to_string()) { + Ok(_number) => { + self.manager = Some(Manager::new(account, TrustMode::OnFirstUse)); } - } + Err(e) => log::warn!("failed to set Account number: {e}"), + }, Err(e) => log::warn!("failed to create new Account: {e}"), } } Err(e) => log::warn!("failed to get phone number: {e}"), } + Err(Error::new(ErrorKind::Other, "not implmented")) } /// Attempts to obtain a phone number from the user. From 0eb1838ada4e140d958312dc3f1967e134c679e8 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 7 Dec 2023 18:36:31 +1100 Subject: [PATCH 05/49] Add Sigchat.account_setup() option to abort (and read Chat Dialogue in pddb) --- apps/sigchat/locales/i18n.json | 4 ++++ apps/sigchat/src/lib.rs | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/sigchat/locales/i18n.json b/apps/sigchat/locales/i18n.json index cf87e40ab..9fd8a69cb 100644 --- a/apps/sigchat/locales/i18n.json +++ b/apps/sigchat/locales/i18n.json @@ -15,6 +15,10 @@ "en": "Please scan with primary Signal device", "en-tts": "Please scan with primary Signal device" }, + "sigchat.account.offline": { + "en": "Read Signal offline", + "en-tts": "Read Signal offline" + }, "sigchat.account.register": { "en": "Register a new Signal Account", "en-tts": "Register a new Signal Account" diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index 0a29b9e80..e71ef27e1 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -70,7 +70,8 @@ impl<'a> SigChat<'a> { Ok(false) } - /// Setup a Signal Account via Registration or Linking + /// Setup a Signal Account via Registration or Linking, + /// or abort setup and read existing chat Dialogues in pddb /// fn account_setup(&mut self) -> Result { log::info!("Attempting to setup a Signal Account"); @@ -80,6 +81,9 @@ impl<'a> SigChat<'a> { self.modals .add_list_item(t!("sigchat.account.register", locales::LANG)) .expect("failed add list item"); + self.modals + .add_list_item(t!("sigchat.account.offline", locales::LANG)) + .expect("failed add list item"); self.modals .get_radiobutton(t!("sigchat.account.title", locales::LANG)) .expect("failed radiobutton modal"); @@ -87,7 +91,10 @@ impl<'a> SigChat<'a> { Ok(index) => match index { 0 => Ok(self.account_link()?), 1 => Ok(self.account_register()?), - + 2 => { + log::info!("account setup aborted"); + Err(Error::new(ErrorKind::Other, "account setup aborted")) + } _ => { log::warn!("invalid index"); Err(Error::new(ErrorKind::Other, "invalid radio index")) From 5be8040e00c82a9e99004f79a1e55f8a9295703d Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 7 Dec 2023 18:40:19 +1100 Subject: [PATCH 06/49] remove redundant sigchat MenuOp::Link & MenueOp::Register alternatives are presented to user as required --- apps/sigchat/src/api.rs | 2 -- apps/sigchat/src/main.rs | 18 ------------------ 2 files changed, 20 deletions(-) diff --git a/apps/sigchat/src/api.rs b/apps/sigchat/src/api.rs index c34b1c86b..bd9e19ae9 100644 --- a/apps/sigchat/src/api.rs +++ b/apps/sigchat/src/api.rs @@ -17,9 +17,7 @@ pub enum SigchatOp { #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] pub enum MenuOp { - Link, Noop, - Register, } #[allow(dead_code)] diff --git a/apps/sigchat/src/main.rs b/apps/sigchat/src/main.rs index 197cd3988..3a476123e 100644 --- a/apps/sigchat/src/main.rs +++ b/apps/sigchat/src/main.rs @@ -61,22 +61,6 @@ fn wrapped_main() -> ! { ); let cid = xous::connect(sid).unwrap(); - chat.menu_add(MenuItem { - name: xous_ipc::String::from_str(t!("sigchat.register", locales::LANG)), - action_conn: Some(cid), - action_opcode: SigchatOp::Menu as u32, - action_payload: MenuPayload::Scalar([MenuOp::Register as u32, 0, 0, 0]), - close_on_select: true, - }) - .expect("failed add menu"); - chat.menu_add(MenuItem { - name: xous_ipc::String::from_str(t!("sigchat.link", locales::LANG)), - action_conn: Some(cid), - action_opcode: SigchatOp::Menu as u32, - action_payload: MenuPayload::Scalar([MenuOp::Link as u32, 0, 0, 0]), - close_on_select: true, - }) - .expect("failed add menu"); chat.menu_add(MenuItem { name: xous_ipc::String::from_str(t!("sigchat.menu.close", locales::LANG)), action_conn: Some(cid), @@ -112,9 +96,7 @@ fn wrapped_main() -> ! { log::info!("got Chat Menu Click"); xous::msg_scalar_unpack!(msg, menu_code, _, _, _, { match FromPrimitive::from_usize(menu_code) { - Some(MenuOp::Link) => sigchat.account_link(), Some(MenuOp::Noop) => {} - Some(MenuOp::Register) => sigchat.account_register(), _ => (), } }); From 90aec71d3370d6e2430e399f71f375033af059f8 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Fri, 8 Dec 2023 05:00:29 +1100 Subject: [PATCH 07/49] add logging to sigchat Event::Focus --- apps/sigchat/src/main.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/sigchat/src/main.rs b/apps/sigchat/src/main.rs index 3a476123e..1744c1657 100644 --- a/apps/sigchat/src/main.rs +++ b/apps/sigchat/src/main.rs @@ -84,7 +84,13 @@ fn wrapped_main() -> ! { Some(Event::Focus) => { if first_focus { first_focus = false; - sigchat.connect(); + match sigchat.connect() { + Ok(true) => log::info!("connected to Signal Account"), + Ok(false) => log::info!("not connected to Signal Account"), + Err(e) => { + log::warn!("error while connecting to Signal Account: {e}") + } + } } sigchat.redraw(); } From 046873cec54aced107e7cc362a4ab600c6eb2b53 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Fri, 8 Dec 2023 05:14:51 +1100 Subject: [PATCH 08/49] add status text to Sigchat::connect() --- apps/sigchat/locales/i18n.json | 12 ++++++++++++ apps/sigchat/src/lib.rs | 24 +++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/apps/sigchat/locales/i18n.json b/apps/sigchat/locales/i18n.json index 9fd8a69cb..2c65ac76d 100644 --- a/apps/sigchat/locales/i18n.json +++ b/apps/sigchat/locales/i18n.json @@ -54,6 +54,18 @@ "ja": "WARNING: pddb not mounted *EN*", "zh": "WARNING: pddb not mounted *EN*" }, + "sigchat.status.connecting": { + "en": "connecting", + "en-tts": "connecting" + }, + "sigchat.status.offline": { + "en": "Signal offline", + "en-tts": "Signal offline" + }, + "sigchat.status.online": { + "en": "Signal online", + "en-tts": "Signal online" + }, "sigchat.wifi.connected": { "en": "WiFi connected", "en-tts": "WiFi connected", diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index e71ef27e1..4ccfb8d58 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -54,20 +54,31 @@ impl<'a> SigChat<'a> { log::info!("Setting up Signal Account Manager"); let account = match Account::read(SIGCHAT_ACCOUNT) { Ok(account) => account, - Err(e) => self.account_setup()?, + Err(_) => self.account_setup()?, }; + self.chat + .set_status_text(t!("sigchat.status.connecting", locales::LANG)); + self.chat.set_busy_state(true); self.manager = Some(Manager::new(account, TrustMode::OnFirstUse)); + self.chat.set_busy_state(false); } - if let Some(manager) = &self.manager { + if self.manager.is_some() { log::info!("Signal Account Manager OK"); + self.chat + .set_status_text(t!("sigchat.status.online", locales::LANG)); + Ok(true) + } else { + log::info!("failed to setup Signal Account Manager"); + self.chat + .set_status_text(t!("sigchat.status.offline", locales::LANG)); + Ok(false) } } else { self.modals .show_notification(t!("sigchat.wifi.warning", locales::LANG), None) .expect("notification failed"); + Ok(false) } - self.dialogue_set(None); - Ok(false) } /// Setup a Signal Account via Registration or Linking, @@ -102,7 +113,10 @@ impl<'a> SigChat<'a> { }, Err(e) => { log::warn!("failed to present account setup radio buttons: {:?}", e); - Err(Error::new(ErrorKind::Other, "failed to present account setup radio buttons")) + Err(Error::new( + ErrorKind::Other, + "failed to present account setup radio buttons", + )) } } } From c9e3f549a49bf7ce2ffbcec6f401d47a025b5fab Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Sun, 10 Dec 2023 18:54:31 +1100 Subject: [PATCH 09/49] sigchat probe the Signal host server for tls certificates - prompt the user for the Signal host server - probe the host for the tls Certificate Authorities chain of trust - allow the user to trust 1 or more Certificate Authorities --- apps/sigchat/locales/i18n.json | 8 ++++ apps/sigchat/src/api.rs | 1 + apps/sigchat/src/lib.rs | 80 ++++++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/apps/sigchat/locales/i18n.json b/apps/sigchat/locales/i18n.json index 2c65ac76d..4e12facc8 100644 --- a/apps/sigchat/locales/i18n.json +++ b/apps/sigchat/locales/i18n.json @@ -3,6 +3,14 @@ "en": "Failed to setup Signal Account", "en-tts": "Failed to setup Signal Account" }, + "sigchat.account.abort": { + "en": "Signal Account setup aborted", + "en-tts": "Signal Account setup aborted" + }, + "sigchat.account.host.name": { + "en": "Please provide the Signal host server name", + "en-tts": "Please provide the Signal host server name" + }, "sigchat.account.link": { "en": "Link to an existing Signal Account", "en-tts": "Link to an existing Signal Account" diff --git a/apps/sigchat/src/api.rs b/apps/sigchat/src/api.rs index bd9e19ae9..aabb47cb6 100644 --- a/apps/sigchat/src/api.rs +++ b/apps/sigchat/src/api.rs @@ -1,5 +1,6 @@ #[allow(dead_code)] pub(crate) const SERVER_NAME_SIGCHAT: &str = "_Signal chat_"; +pub(crate) const DEFAULT_HOST_NAME: &str = "signal.org"; #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] pub enum SigchatOp { diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index 4ccfb8d58..97667ec22 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -100,8 +100,32 @@ impl<'a> SigChat<'a> { .expect("failed radiobutton modal"); match self.modals.get_radio_index() { Ok(index) => match index { - 0 => Ok(self.account_link()?), - 1 => Ok(self.account_register()?), + 0 => { + let host = self.name_modal( + DEFAULT_HOST_NAME, + t!("sigchat.account.host.name", locales::LANG), + ); + match self.probe_host(&host) { + true => Ok(self.account_link(&host)?), + false => Err(Error::new( + ErrorKind::Other, + "failed to trust host certificate", + )), + } + } + 1 => { + let host = self.name_modal( + DEFAULT_HOST_NAME, + t!("sigchat.account.host.name", locales::LANG), + ); + match self.probe_host(&host) { + true => Ok(self.account_register(&host)?), + false => Err(Error::new( + ErrorKind::Other, + "failed to trust host certificate", + )), + } + } 2 => { log::info!("account setup aborted"); Err(Error::new(ErrorKind::Other, "account setup aborted")) @@ -121,24 +145,72 @@ impl<'a> SigChat<'a> { } } + /// Probe host for tls Certificate Authority chain of trust + /// + /// # Arguments + /// * `host` - the dns name or ip address of a Signal server + /// + /// # Returns + /// true if at least 1 Certificate Authority is trusted + /// + fn probe_host(&self, host: &str) -> bool { + let tls = Tls::new(); + match tls.probe(host) { + Ok(0) => { + self.modals + .show_notification(t!("sigchat.account.abort", locales::LANG), None) + .expect("abort failed"); + false + } + Ok(_count) => true, + Err(e) => { + self.modals + .show_notification(&format!("{}", e), None) + .expect("qrcode failed"); + false + } + } + } + /// Link this device to an existing Signal Account /// /// Signal allows to link additional devices to your primary device (e.g. Signal-Android). /// Note that currently Signal allows up to three linked devices per primary. /// - pub fn account_link(&self) { + pub fn account_link(&mut self, host: &str) -> Result { self.modals .show_notification(&"sorry - linking is not implemented yet", None) .expect("notification failed"); } + /// Prompt a name from the user + /// + /// # Arguments + /// * `default_name` - A name suggested to the user + /// * `prompt` - A prompt to explain what is being requested + /// + /// # Returns + /// the name provided by the user + /// + fn name_modal(&self, default_name: &str, prompt: &str) -> String { + match self + .modals + .alert_builder(prompt) + .field(Some(default_name.to_string()), None) + .build() + { + Ok(text) => text.content()[0].content.to_string(), + _ => default_name.to_string(), + } + } + /// Register a new Signal Account with this as the primary device. /// /// A Signal Account requires a phone number to receive SMS or incoming calls for registration & validation. /// The phone number must include the country calling code, i.e. the number must start with a "+" sign. /// Warning: this will disable the authentication of your phone as a primary device. /// - pub fn account_register(&mut self) -> Result { + pub fn account_register(&mut self, host: &str) -> Result { log::info!("Attempting to Register a new Signal Account"); self.modals .show_notification(&"sorry - registration is not implemented yet", None) From 572dcd6bd136b8abeca7cf57b5184b0e7d298529 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Sun, 10 Dec 2023 19:19:56 +1100 Subject: [PATCH 10/49] implement Sigchat.account_link() - create a new Account in the pddb - create an Account Manager to coordinate the Link - request a name for the device being linked (ie default 'xous') - call Manager.link() - on failure - delete the pddb Account record --- apps/sigchat/src/account.rs | 12 ++++++++++++ apps/sigchat/src/api.rs | 1 + apps/sigchat/src/lib.rs | 39 +++++++++++++++++++++++++++++++++---- apps/sigchat/src/manager.rs | 5 +++-- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/apps/sigchat/src/account.rs b/apps/sigchat/src/account.rs index b8fc5f16b..1a301969b 100644 --- a/apps/sigchat/src/account.rs +++ b/apps/sigchat/src/account.rs @@ -54,6 +54,18 @@ impl Account { } } + /// Delete this Account key/value from the pddb + /// + /// While this Account struct will persist in memory, a subsequent Account.read() will fail + /// + pub fn delete(pddb_dict: &str) -> Result<(), Error> { + let pddb = pddb::Pddb::new(); + pddb.try_mount(); + pddb.delete_dict(pddb_dict, None)?; + log::info!("deleted Signal Account from pddb"); + Ok(()) + } + #[allow(dead_code)] pub fn number(&self) -> Option<&str> { match &self.number { diff --git a/apps/sigchat/src/api.rs b/apps/sigchat/src/api.rs index aabb47cb6..01522cc62 100644 --- a/apps/sigchat/src/api.rs +++ b/apps/sigchat/src/api.rs @@ -1,5 +1,6 @@ #[allow(dead_code)] pub(crate) const SERVER_NAME_SIGCHAT: &str = "_Signal chat_"; +pub(crate) const DEFAULT_DEVICE_NAME: &str = "xous"; pub(crate) const DEFAULT_HOST_NAME: &str = "signal.org"; #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index 97667ec22..c5bad46ff 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -178,9 +178,40 @@ impl<'a> SigChat<'a> { /// Note that currently Signal allows up to three linked devices per primary. /// pub fn account_link(&mut self, host: &str) -> Result { - self.modals - .show_notification(&"sorry - linking is not implemented yet", None) - .expect("notification failed"); + log::info!("Attempting to Link to an existing Signal Account"); + match Account::new(SIGCHAT_ACCOUNT) { + Ok(account) => { + let mut manager = Manager::new(account, TrustMode::OnFirstUse); + let name = self.name_modal( + DEFAULT_DEVICE_NAME, + t!("sigchat.account.link.name", locales::LANG), + ); + match manager.link(Some(&name), Some(&host)) { + Ok(true) => { + log::info!("Linked Signal Account"); + } + Ok(false) => { + log::info!("failed to link Signal Account"); + } + Err(e) => { + log::warn!("error while linking Signal Account: {e}"); + Account::delete(SIGCHAT_ACCOUNT).unwrap_or_else(|e| { + log::warn!("failed to delete unregistered account from pddb: {e}") + }); + self.modals + .show_notification(&format!("{}", e), None) + .expect("notification failed"); + } + } + } + Err(_) => { + log::warn!("failed to create new Account in pddb: {}", SIGCHAT_ACCOUNT); + self.modals + .show_notification(t!("sigchat.account.failed", locales::LANG), None) + .expect("notification failed"); + } + }; + Ok(Account::read(SIGCHAT_ACCOUNT)?) } /// Prompt a name from the user @@ -210,7 +241,7 @@ impl<'a> SigChat<'a> { /// The phone number must include the country calling code, i.e. the number must start with a "+" sign. /// Warning: this will disable the authentication of your phone as a primary device. /// - pub fn account_register(&mut self, host: &str) -> Result { + pub fn account_register(&mut self, _host: &str) -> Result { log::info!("Attempting to Register a new Signal Account"); self.modals .show_notification(&"sorry - registration is not implemented yet", None) diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index 04c723b97..f9e06f3c9 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -123,10 +123,11 @@ impl Manager { /// Link to an existing device, instead of registering a new number. This shows a "sgnl://linkdevice?uuid=…​" URI. If you want to connect to another signal-cli instance, you can just use this URI. If you want to link to an Android/iOS device, create a QR code with the URI (e.g. with qrencode) and scan that in the Signal app. /// /// # Arguments - /// * `name` - Optionally specify a name to describe this new device. By default "cli" will be used. + /// * `name` - Optionally specify a name to describe this new device (defaults to "xous"). + /// * `host` - Optionally specify a host to connect to (defaults to "signal.org"). /// #[allow(dead_code)] - pub fn link(_name: &str) -> Result<(), Error> { + pub fn link(&mut self, name: Option<&str>, host: Option<&str>) -> Result { todo!(); } From e5bb114c71895375770ebb055a18dce3f0267825 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Mon, 11 Dec 2023 06:19:36 +1100 Subject: [PATCH 11/49] add sigchat doco --- apps/sigchat/src/lib.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index c5bad46ff..fe0f95466 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -47,6 +47,14 @@ impl<'a> SigChat<'a> { /// Connect to the Signal servers /// + /// The process first waits for an active WiFi connection, and then + /// initiates a Signal Account Manager with the Signal Account struct stored + /// in the pddb (or kicks off the Account setup process otherwise). The + /// Account Manager orchestrates the interaction with the Signal host server. + /// + /// # Returns + /// true on a successful connection to a Signal Account/Server + /// pub fn connect(&mut self) -> Result { log::info!("Attempting connect to Signal server"); if self.wifi() { @@ -84,6 +92,16 @@ impl<'a> SigChat<'a> { /// Setup a Signal Account via Registration or Linking, /// or abort setup and read existing chat Dialogues in pddb /// + /// The user can choose to: + /// 1. Link to an existing Signal Account + /// 2. Register a new Signal Account + /// 3. Abort account setup to read existing Signal Dialogues stored in the pddb + /// The online options involve nominating the Signal host server, + /// and probing the host for trusted tlls Certificate Authorities. + /// + /// # Returns + /// Account struct stored in pddb + /// fn account_setup(&mut self) -> Result { log::info!("Attempting to setup a Signal Account"); self.modals @@ -177,6 +195,15 @@ impl<'a> SigChat<'a> { /// Signal allows to link additional devices to your primary device (e.g. Signal-Android). /// Note that currently Signal allows up to three linked devices per primary. /// + /// The user must provide a name for the current device before the Link process + /// commences - culminating by presenting a qr-code to be scanned by the primary device. + /// + /// # Arguments + /// * `host` - the Signal server host name or ip-address + /// + /// # Returns + /// Account struct stored in pddb + /// pub fn account_link(&mut self, host: &str) -> Result { log::info!("Attempting to Link to an existing Signal Account"); match Account::new(SIGCHAT_ACCOUNT) { @@ -306,6 +333,9 @@ impl<'a> SigChat<'a> { /// If wifi is not connected then a modal offers to "Connect to wifi?" /// and tries for 10 seconds before representing. /// + /// # Returns + /// true when wifi is connected + /// pub fn wifi(&self) -> bool { if HOSTED_MODE { return true; From 2be426131c648757c66724724dea77f2cbc90a82 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Mon, 11 Dec 2023 06:21:07 +1100 Subject: [PATCH 12/49] add sigchat busy status text --- apps/sigchat/locales/i18n.json | 4 ++++ apps/sigchat/src/lib.rs | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/sigchat/locales/i18n.json b/apps/sigchat/locales/i18n.json index 4e12facc8..3d9df3197 100644 --- a/apps/sigchat/locales/i18n.json +++ b/apps/sigchat/locales/i18n.json @@ -74,6 +74,10 @@ "en": "Signal online", "en-tts": "Signal online" }, + "sigchat.status.probing": { + "en": "probing host for trusted tls", + "en-tts": "probing host for trusted tls" + }, "sigchat.wifi.connected": { "en": "WiFi connected", "en-tts": "WiFi connected", diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index fe0f95466..46793d67b 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -172,16 +172,24 @@ impl<'a> SigChat<'a> { /// true if at least 1 Certificate Authority is trusted /// fn probe_host(&self, host: &str) -> bool { + self.chat + .set_status_text(t!("sigchat.status.probing", locales::LANG)); + self.chat.set_busy_state(true); let tls = Tls::new(); match tls.probe(host) { Ok(0) => { self.modals .show_notification(t!("sigchat.account.abort", locales::LANG), None) .expect("abort failed"); + self.chat.set_busy_state(false); false } - Ok(_count) => true, + Ok(_count) => { + self.chat.set_busy_state(false); + true + } Err(e) => { + self.chat.set_busy_state(false); self.modals .show_notification(&format!("{}", e), None) .expect("qrcode failed"); @@ -213,18 +221,24 @@ impl<'a> SigChat<'a> { DEFAULT_DEVICE_NAME, t!("sigchat.account.link.name", locales::LANG), ); + self.chat + .set_status_text(t!("sigchat.status.connecting", locales::LANG)); + self.chat.set_busy_state(true); match manager.link(Some(&name), Some(&host)) { Ok(true) => { log::info!("Linked Signal Account"); + self.chat.set_busy_state(false); } Ok(false) => { log::info!("failed to link Signal Account"); + self.chat.set_busy_state(false); } Err(e) => { log::warn!("error while linking Signal Account: {e}"); Account::delete(SIGCHAT_ACCOUNT).unwrap_or_else(|e| { log::warn!("failed to delete unregistered account from pddb: {e}") }); + self.chat.set_busy_state(false); self.modals .show_notification(&format!("{}", e), None) .expect("notification failed"); From b094402bf3c77c572b552c9e4efcd8ca8eccacec Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 12 Dec 2023 06:44:02 +1100 Subject: [PATCH 13/49] sigchat prepare tls connection to provisioning server --- apps/sigchat/src/manager.rs | 27 ++++++- apps/sigchat/src/manager/signal_ws.rs | 102 ++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 apps/sigchat/src/manager/signal_ws.rs diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index f9e06f3c9..6abeb80b5 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -1,11 +1,13 @@ mod group_permission; mod link_state; +mod signal_ws; mod trust_mode; -use crate::Account; +use crate::{Account, DEFAULT_DEVICE_NAME, DEFAULT_HOST_NAME}; use group_permission::GroupPermission; use link_state::LinkState; use std::io::Error; +use signal_ws::SignalWS; pub use trust_mode::TrustMode; // Structure modeled on signal-cli by AsamK and contributors - https://github.com/AsamK/signal-cli. @@ -14,8 +16,9 @@ pub use trust_mode::TrustMode; #[allow(dead_code)] pub struct Manager { - account:Account, + account: Account, trust_mode: TrustMode, + websocket: Option, log_verbose: bool, log_scrub: bool, log_send: bool, @@ -32,6 +35,7 @@ impl Manager { Manager { account, trust_mode, + websocket: None, log_verbose: false, log_scrub: false, log_send: false, @@ -128,7 +132,24 @@ impl Manager { /// #[allow(dead_code)] pub fn link(&mut self, name: Option<&str>, host: Option<&str>) -> Result { - todo!(); + // insert default values as required + let name = name.unwrap_or(DEFAULT_DEVICE_NAME); + let host = host.unwrap_or(DEFAULT_HOST_NAME); + match SignalWS::provision(&host) { + Ok(ws) => { + if let Some(prior_ws) = &mut self.websocket { + prior_ws.close(); + } + self.websocket = Some(ws); + + log::info!("websocket established to link {name} at {host}"); + log::warn!("Manager.link() not fully implemented."); + } + Err(e) => { + log::info!("failed to connect to server: {}", e); + Err(e) + } + } } /// Link another device to this device. Only works, if this is the primary device. diff --git a/apps/sigchat/src/manager/signal_ws.rs b/apps/sigchat/src/manager/signal_ws.rs new file mode 100644 index 000000000..92f08cfff --- /dev/null +++ b/apps/sigchat/src/manager/signal_ws.rs @@ -0,0 +1,102 @@ +use rustls::{ClientConnection, StreamOwned}; +use std::io::{Error, ErrorKind}; +use std::net::TcpStream; +use tls::Tls; + +const PROVISIONING_PATH: &str = "/provisioning/"; +const REGISTRATION_PATH: &str = "/v1/registration"; + +#[allow(dead_code)] +pub enum SignalWS { + Message { + ws: WebSocket>, + }, + Provision { + ws: WebSocket>, + }, + Register { + ws: WebSocket>, + }, +} + +impl SignalWS { + #[allow(dead_code)] + pub fn message(_host: &str) -> Result { + todo!(); + } + + pub fn provision(host: &str) -> Result { + let server = format!("wss://{host}{}", PROVISIONING_PATH); + // let mut conn = client_connection(&server); + match SignalWS::connect(server) { + Ok(ws) => Ok(Self::Provision { ws }), + Err(e) => Err(e), + } + } + + #[allow(dead_code)] + pub fn register(host: &str) -> Result { + let server = format!("wss://{host}{}", REGISTRATION_PATH); + // let mut conn = client_connection(&server); + match SignalWS::connect(server) { + Ok(ws) => Ok(Self::Register { ws }), + Err(e) => Err(e), + } + } + + pub fn close(&mut self) { + match self { + SignalWS::Message { ws, .. } + | SignalWS::Provision { ws, .. } + | SignalWS::Register { ws, .. } => { + ws.close(None) + .unwrap_or_else(|e| log::warn!("failed to close websocket: {e}")); + // TODO close properly https://docs.rs/tungstenite/0.20.1/tungstenite/protocol/struct.WebSocket.html + ws.flush() + .unwrap_or_else(|e| log::warn!("failed to flush websocket: {e}")); + } + } + } + + /// Make a websocket connection to host server + /// + /// # Arguments + /// * `server` - server + /// + /// # Returns + /// + fn connect( + server: String, + ) -> Result>, Error> { + log::info!("connecting websocket"); + match url::Url::parse(&server) { + Ok(url) => { + let host = url.host_str().expect("failed to extract host from url"); + match TcpStream::connect((host, 443)) { + Ok(sock) => { + log::info!("tcp connected"); + let xtls = Tls::new(); + match xtls.stream(host, sock) { + Ok(tls_stream) => { + log::info!("tls configured"); + log::info!("WEBSOCKET NOT IMPLEMENTED YET"); + } + Err(e) => { + log::warn!("failed to configure tls: {e}"); + Err(e) + } + } + } + Err(e) => { + log::warn!("failed to connect tcp: {e}"); + Err(e) + } + } + } + Err(e) => { + log::info!("failed to parse server url: {e}"); + Err(Error::new(ErrorKind::InvalidData, e)) + } + } + } +} \ No newline at end of file From bcb6a8c3bf9c603032e3d0e729ed88a09eaca829 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Mon, 18 Dec 2023 17:16:33 +1100 Subject: [PATCH 14/49] implement tls.stream_owned() --- apps/sigchat/src/manager/signal_ws.rs | 4 ++-- libs/tls/src/lib.rs | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/sigchat/src/manager/signal_ws.rs b/apps/sigchat/src/manager/signal_ws.rs index 92f08cfff..65b942062 100644 --- a/apps/sigchat/src/manager/signal_ws.rs +++ b/apps/sigchat/src/manager/signal_ws.rs @@ -74,9 +74,9 @@ impl SignalWS { let host = url.host_str().expect("failed to extract host from url"); match TcpStream::connect((host, 443)) { Ok(sock) => { - log::info!("tcp connected"); + log::info!("tcp connected to {host}"); let xtls = Tls::new(); - match xtls.stream(host, sock) { + match xtls.stream_owned(host, sock) { Ok(tls_stream) => { log::info!("tls configured"); log::info!("WEBSOCKET NOT IMPLEMENTED YET"); diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index ea6b549ca..642ab5850 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -11,7 +11,7 @@ use rkyv::{ ser::{serializers::WriteSerializer, Serializer}, Deserialize, }; -use rustls::{Certificate, ClientConfig, RootCertStore}; +use rustls::{Certificate, ClientConfig, ClientConnection, RootCertStore}; use sha2::Digest; use std::convert::{Into, TryFrom, TryInto}; use std::io::{Error, ErrorKind, Read, Write}; @@ -342,6 +342,16 @@ impl Tls { .with_root_certificates(self.root_store()) .with_no_client_auth() } + + pub fn stream_owned(&self, host: &str, sock: TcpStream) -> Result, Error> { + match rustls::ClientConnection::new( + Arc::new(self.client_config()), + host.try_into().expect("failed url host_str"), + ) { + Ok(conn) => Ok(rustls::StreamOwned::new(conn, sock)), + Err(_) => Err(Error::new(ErrorKind::Other, "failed to configure client connection")) + } + } } // https://stackoverflow.com/questions/57029974/how-to-split-string-into-chunks-in-rust-to-insert-spaces From de6b6baab98988b5d915173fa7429cf37017ae07 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Mon, 18 Dec 2023 18:35:39 +1100 Subject: [PATCH 15/49] extend shellchat `net ws` to utilize `/lib/tls` --- libs/tls/src/lib.rs | 4 +-- services/shellchat/Cargo.toml | 2 +- services/shellchat/src/cmds/net_cmd.rs | 47 ++++++++++++++++++++------ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index 642ab5850..614d5c1a7 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -227,8 +227,8 @@ impl Tls { let archive = unsafe { rkyv::archived_value::(&bytes, pos) }; let ta = archive.deserialize(&mut AllocDeserializer {}).ok(); - log::info!("get {}", key); - log::trace!("get '{}' = '{:?}'", key, &ta); + log::info!("get trust anchor {}", key); + log::trace!("get trust anchor'{}' = '{:?}'", key, &ta); ta } Err(e) => { diff --git a/services/shellchat/Cargo.toml b/services/shellchat/Cargo.toml index fdf0d6539..936ac2c71 100644 --- a/services/shellchat/Cargo.toml +++ b/services/shellchat/Cargo.toml @@ -82,7 +82,7 @@ url = {version = "2.2.2", optional = true} ring = {version = "0.16.20", optional = true} # for websocket testing -tungstenite = {version = "0.20.0", optional = true, features = ["rustls-tls-webpki-roots"]} +tungstenite = {version = "0.20.0", optional = true} # for performance testing perflib = {path="../../tools/perflib", optional = true} diff --git a/services/shellchat/src/cmds/net_cmd.rs b/services/shellchat/src/cmds/net_cmd.rs index da7eb557c..c859ad721 100644 --- a/services/shellchat/src/cmds/net_cmd.rs +++ b/services/shellchat/src/cmds/net_cmd.rs @@ -2,13 +2,13 @@ use crate::{CommonEnv, ShellCmdApi}; use com::api::NET_MTU; use net::NetPingCallback; use num_traits::*; -use std::io::Read; -use std::io::Write; +use std::io::{Read, Write}; use std::net::{IpAddr, TcpListener, TcpStream}; use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; +use tls::Tls; use xous::MessageEnvelope; use xous_ipc::String; @@ -415,16 +415,41 @@ impl<'a> ShellCmdApi<'a> for NetCmd { #[cfg(feature = "websocket")] "ws" => { if self.ws.is_none() { - let (socket, response) = - tungstenite::connect(url::Url::parse("wss://awake.noskills.club/ws").unwrap()).expect("Can't connect"); - - log::info!("Connected to the server"); - log::info!("Response HTTP code: {}", response.status()); - log::info!("Response contains the following headers:"); - for (ref header, _value) in response.headers() { - log::info!("* {}", header); + let server = "wss://awake.noskills.club/ws"; + let url = url::Url::parse(server).expect("Can't parse"); + log::info!("attempting websocket connection to {}", url.as_str()); + let host = url.host_str().expect("failed to extract host from url"); + match TcpStream::connect((host, 443)) { + Ok(sock) => { + log::info!("tcp connected to {host}"); + let xtls = Tls::new(); + match xtls.stream_owned(host, sock) { + Ok(tls_stream) => { + log::info!("tls configured"); + match tungstenite::client(url, tls_stream) { + Ok((_socket, response)) => { + log::info!("Websocket connected to: {}", server); + log::info!( + "Response HTTP code: {}", + response.status() + ); + log::info!( + "Response contains the following headers:" + ); + for (ref header, _value) in response.headers() { + log::info!("* {}", header); + } + } + Err(e) => { + log::info!("failed to connect websocket: {}", e) + } + } + } + Err(e) => log::warn!("failed to configure tls: {e}"), + } + } + Err(e) => log::warn!("failed to connect tcp: {e}"), } - self.ws = Some(socket); } let mut err = false; if let Some(socket) = &mut self.ws { From 8fddba48812b26ca619e35b7bf4292b7ae7e6ff2 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 19 Dec 2023 05:15:43 +1100 Subject: [PATCH 16/49] implement sigchat provisioning websocket with tungstenite --- apps/sigchat/Cargo.toml | 1 + apps/sigchat/src/api.rs | 2 +- apps/sigchat/src/manager/signal_ws.rs | 16 ++++++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/sigchat/Cargo.toml b/apps/sigchat/Cargo.toml index 5f23a1631..dfa83e63c 100644 --- a/apps/sigchat/Cargo.toml +++ b/apps/sigchat/Cargo.toml @@ -36,6 +36,7 @@ rkyv = {version = "0.4.3", default-features = false, features = ["const_generics rustls = { version = "0.21.7" } serde = { version = "1.0", features = [ "derive" ] } tls = { path = "../../libs/tls" } +tungstenite = "0.20.1" ureq = { version = "2.7.1", features = ["json"] } url = "2.2.2" diff --git a/apps/sigchat/src/api.rs b/apps/sigchat/src/api.rs index 01522cc62..e7f850f0d 100644 --- a/apps/sigchat/src/api.rs +++ b/apps/sigchat/src/api.rs @@ -1,7 +1,7 @@ #[allow(dead_code)] pub(crate) const SERVER_NAME_SIGCHAT: &str = "_Signal chat_"; pub(crate) const DEFAULT_DEVICE_NAME: &str = "xous"; -pub(crate) const DEFAULT_HOST_NAME: &str = "signal.org"; +pub(crate) const DEFAULT_HOST_NAME: &str = "chat.signal.org"; #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] pub enum SigchatOp { diff --git a/apps/sigchat/src/manager/signal_ws.rs b/apps/sigchat/src/manager/signal_ws.rs index 65b942062..7e304b14f 100644 --- a/apps/sigchat/src/manager/signal_ws.rs +++ b/apps/sigchat/src/manager/signal_ws.rs @@ -2,8 +2,9 @@ use rustls::{ClientConnection, StreamOwned}; use std::io::{Error, ErrorKind}; use std::net::TcpStream; use tls::Tls; +use tungstenite::WebSocket; -const PROVISIONING_PATH: &str = "/provisioning/"; +const PROVISIONING_PATH: &str = "/v1/websocket/provisioning/"; const REGISTRATION_PATH: &str = "/v1/registration"; #[allow(dead_code)] @@ -71,6 +72,7 @@ impl SignalWS { log::info!("connecting websocket"); match url::Url::parse(&server) { Ok(url) => { + log::info!("attempting websocket connection to {}", url.as_str()); let host = url.host_str().expect("failed to extract host from url"); match TcpStream::connect((host, 443)) { Ok(sock) => { @@ -79,7 +81,17 @@ impl SignalWS { match xtls.stream_owned(host, sock) { Ok(tls_stream) => { log::info!("tls configured"); - log::info!("WEBSOCKET NOT IMPLEMENTED YET"); + match tungstenite::client(url, tls_stream) { + Ok((socket, response)) => { + log::info!("Websocket connected to: {}", server); + log::info!("Response HTTP code: {}", response.status()); + Ok(socket) + } + Err(e) => { + log::info!("failed to connect websocket: {}", e); + Err(Error::from(ErrorKind::ConnectionRefused)) + } + } } Err(e) => { log::warn!("failed to configure tls: {e}"); From d22317018b4ca4719c6fdbc7cafd0e957dc90db8 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 19 Dec 2023 06:03:23 +1100 Subject: [PATCH 17/49] rename Tls.trust_modal() --- libs/tls/README.md | 4 ++-- libs/tls/src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/tls/README.md b/libs/tls/README.md index 634657a09..186bfbb53 100644 --- a/libs/tls/README.md +++ b/libs/tls/README.md @@ -8,13 +8,13 @@ The tls handshake involves the host offering the client a Certificate signed for It does not seem in keeping with the Precursor/Xous idea to ask users to trust any such list of Root CA Certificates. -This tls library enables the user to obtain and save trusted CA certificates to the PDDB as a personalised set of trusted Root CA certificates on which to establish a tls connection. This can be as simple for the user as responding to a modal, when making a first connection to an as yet un-trusted host. Just a couple of clicks. The developer need only call `Tls::check_trust()`. +This tls library enables the user to obtain and save trusted CA certificates to the PDDB as a personalised set of trusted Root CA certificates on which to establish a tls connection. This can be as simple for the user as responding to a modal, when making a first connection to an as yet un-trusted host. Just a couple of clicks. The developer need only call `Tls::trust_modal()`. Note that hosts with self-signed Certificates may be probed and trusted (e.g. [chat.signal.org](https://chat.signal.org)) A small set of shellchat commands are included to enact the base functionality: -- `net tls probe ` will initiate a modified tls handshake with ``, obtain the certificate chain offered by ``, and immediately terminate the connection. A call to Tls::check_trust() will present the CA certificate chain in a modal to be individually selected and saved to PDDB if trusted. +- `net tls probe ` will initiate a modified tls handshake with ``, obtain the certificate chain offered by ``, and immediately terminate the connection. A call to Tls::trust_modal() will present the CA certificate chain in a modal to be individually selected and saved to PDDB if trusted. - `net tls test ` will attempt a normal tls handshake with `` based on the trusted Root CA certificates in the PDDB. If the connection is successful, then a simple `get` is emitted, the response accepted, and the connection closed. - `net tls mozilla` trusts and saves all Root CA's in the [webpki-roots crate](https://crates.io/crates/webpki-roots) - which contains Mozilla's root certificates. (requires `--feature rootCA`) - `net list` lists all trusted certificates in the PDDB diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index 614d5c1a7..9b47719ab 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -45,7 +45,7 @@ impl Tls { /// /// a count of trusted certificates /// - pub fn check_trust(&self, certificates: &[Certificate]) -> usize { + pub fn trust_modal(&self, certificates: &[Certificate]) -> usize { let xns = XousNames::new().unwrap(); let modals = Modals::new(&xns).unwrap(); @@ -319,7 +319,7 @@ impl Tls { } conn.send_close_notify(); match conn.peer_certificates() { - Some(certificates) => Ok(self.check_trust(certificates)), + Some(certificates) => Ok(self.trust_modal(certificates)), None => Ok(0), } } From 96a59422b1c12a3ed030f0c7c0de0da3f52d45ee Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 19 Dec 2023 06:18:08 +1100 Subject: [PATCH 18/49] fix use Tls --- apps/sigchat/src/lib.rs | 3 ++- services/shellchat/src/cmds/net_cmd.rs | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index 46793d67b..db37bb433 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -9,6 +9,7 @@ use chat::Chat; use locales::t; use modals::Modals; use std::io::{Error, ErrorKind}; +use tls::Tls; /// PDDB Dict for sigchat keys const SIGCHAT_ACCOUNT: &str = "sigchat.account"; @@ -263,7 +264,7 @@ impl<'a> SigChat<'a> { /// /// # Returns /// the name provided by the user - /// + /// fn name_modal(&self, default_name: &str, prompt: &str) -> String { match self .modals diff --git a/services/shellchat/src/cmds/net_cmd.rs b/services/shellchat/src/cmds/net_cmd.rs index c859ad721..8c74e736e 100644 --- a/services/shellchat/src/cmds/net_cmd.rs +++ b/services/shellchat/src/cmds/net_cmd.rs @@ -8,7 +8,6 @@ use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; -use tls::Tls; use xous::MessageEnvelope; use xous_ipc::String; @@ -22,7 +21,10 @@ use net::XousServerId; use perflib::*; #[cfg(feature = "websocket")] -use tungstenite::{stream::MaybeTlsStream, WebSocket}; +use { + tls::Tls, + tungstenite::{stream::MaybeTlsStream, WebSocket}, +}; pub struct NetCmd { callback_id: Option, From a8547ce51c8055eb149d8cebaf95c40097f194e5 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 19 Dec 2023 17:46:50 +1100 Subject: [PATCH 19/49] restructure SignalWS --- apps/sigchat/src/manager.rs | 2 +- apps/sigchat/src/manager/signal_ws.rs | 47 +++++++++++---------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index 6abeb80b5..3fea464b8 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -135,7 +135,7 @@ impl Manager { // insert default values as required let name = name.unwrap_or(DEFAULT_DEVICE_NAME); let host = host.unwrap_or(DEFAULT_HOST_NAME); - match SignalWS::provision(&host) { + match SignalWS::new_provision(&host) { Ok(ws) => { if let Some(prior_ws) = &mut self.websocket { prior_ws.close(); diff --git a/apps/sigchat/src/manager/signal_ws.rs b/apps/sigchat/src/manager/signal_ws.rs index 7e304b14f..625ce1b0c 100644 --- a/apps/sigchat/src/manager/signal_ws.rs +++ b/apps/sigchat/src/manager/signal_ws.rs @@ -2,60 +2,51 @@ use rustls::{ClientConnection, StreamOwned}; use std::io::{Error, ErrorKind}; use std::net::TcpStream; use tls::Tls; -use tungstenite::WebSocket; +use tungstenite::{Message, WebSocket}; const PROVISIONING_PATH: &str = "/v1/websocket/provisioning/"; const REGISTRATION_PATH: &str = "/v1/registration"; #[allow(dead_code)] -pub enum SignalWS { - Message { - ws: WebSocket>, - }, - Provision { - ws: WebSocket>, - }, - Register { - ws: WebSocket>, - }, +pub struct SignalWS { + ws: WebSocket>, } impl SignalWS { #[allow(dead_code)] - pub fn message(_host: &str) -> Result { + pub fn new_message(_host: &str) -> Result { todo!(); } - pub fn provision(host: &str) -> Result { + pub fn new_provision(host: &str) -> Result { let server = format!("wss://{host}{}", PROVISIONING_PATH); - // let mut conn = client_connection(&server); match SignalWS::connect(server) { - Ok(ws) => Ok(Self::Provision { ws }), + Ok(ws) => Ok(Self { ws }), Err(e) => Err(e), } } #[allow(dead_code)] - pub fn register(host: &str) -> Result { + pub fn new_register(host: &str) -> Result { let server = format!("wss://{host}{}", REGISTRATION_PATH); - // let mut conn = client_connection(&server); match SignalWS::connect(server) { - Ok(ws) => Ok(Self::Register { ws }), + Ok(ws) => Ok(Self { ws }), Err(e) => Err(e), } } pub fn close(&mut self) { - match self { - SignalWS::Message { ws, .. } - | SignalWS::Provision { ws, .. } - | SignalWS::Register { ws, .. } => { - ws.close(None) - .unwrap_or_else(|e| log::warn!("failed to close websocket: {e}")); - // TODO close properly https://docs.rs/tungstenite/0.20.1/tungstenite/protocol/struct.WebSocket.html - ws.flush() - .unwrap_or_else(|e| log::warn!("failed to flush websocket: {e}")); - } + self.ws + .close(None) + .unwrap_or_else(|e| log::warn!("failed to close websocket: {e}")); + // TODO close properly https://docs.rs/tungstenite/0.20.1/tungstenite/protocol/struct.WebSocket.html + self.ws + .flush() + .unwrap_or_else(|e| log::warn!("failed to flush websocket: {e}")); + } + + + } } From fd5a55ccbdba97273d92d89a7ced876e029c5c69 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 19 Dec 2023 19:17:52 +1100 Subject: [PATCH 20/49] sigchat obtain uuid from Signal Provisioning server --- apps/sigchat/src/manager.rs | 27 ++++++++++++++++++++------- apps/sigchat/src/manager/signal_ws.rs | 9 +++++++-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index 3fea464b8..179932844 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -8,7 +8,9 @@ use group_permission::GroupPermission; use link_state::LinkState; use std::io::Error; use signal_ws::SignalWS; +use std::io::{Error, ErrorKind}; pub use trust_mode::TrustMode; +use tungstenite::Message; // Structure modeled on signal-cli by AsamK and contributors - https://github.com/AsamK/signal-cli. // signal-cli is a commandline interface for libsignal-service-java. It supports registering, verifying, sending and receiving messages. @@ -135,16 +137,27 @@ impl Manager { // insert default values as required let name = name.unwrap_or(DEFAULT_DEVICE_NAME); let host = host.unwrap_or(DEFAULT_HOST_NAME); + let link_err = Error::new(ErrorKind::Other, "failed to link device",); match SignalWS::new_provision(&host) { - Ok(ws) => { - if let Some(prior_ws) = &mut self.websocket { - prior_ws.close(); + Ok(mut ws) => { + log::info!("provisioning websocket established to {host}"); + match ws.read() { + Ok(Message::Binary(uuid)) => { + log::info!("raw uuid ProtoBuffer: {:?}", uuid); + ws.close(); + Ok(false) + } + Ok(_) => { + log::warn!("unexpected Provisioning msg."); + Err(link_err) + } + Err(e) => { + log::warn!("{e}"); + Err(link_err) + } } - self.websocket = Some(ws); + } - log::info!("websocket established to link {name} at {host}"); - log::warn!("Manager.link() not fully implemented."); - } Err(e) => { log::info!("failed to connect to server: {}", e); Err(e) diff --git a/apps/sigchat/src/manager/signal_ws.rs b/apps/sigchat/src/manager/signal_ws.rs index 625ce1b0c..3e1f06098 100644 --- a/apps/sigchat/src/manager/signal_ws.rs +++ b/apps/sigchat/src/manager/signal_ws.rs @@ -45,8 +45,13 @@ impl SignalWS { .unwrap_or_else(|e| log::warn!("failed to flush websocket: {e}")); } - - + pub fn read(&mut self) -> Result { + match self.ws.read() { + Ok(msg) => Ok(msg), + Err(e) => { + log::warn!("{e}"); + Err(Error::new(ErrorKind::Other, "error on reading ws")) + } } } From 36e7798d61dd2026bbb123d8a37070434575de34 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 19 Dec 2023 19:28:57 +1100 Subject: [PATCH 21/49] display sigchat device linking qr-code --- apps/sigchat/locales/i18n.json | 4 ++-- apps/sigchat/src/manager.rs | 29 ++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/sigchat/locales/i18n.json b/apps/sigchat/locales/i18n.json index 3d9df3197..a7a03527e 100644 --- a/apps/sigchat/locales/i18n.json +++ b/apps/sigchat/locales/i18n.json @@ -20,8 +20,8 @@ "en-tts": "Please provide a name for this device" }, "sigchat.account.link.scan": { - "en": "Please scan with primary Signal device", - "en-tts": "Please scan with primary Signal device" + "en": "Please scan with primary Signal device, and then", + "en-tts": "Please scan with primary Signal device, and then" }, "sigchat.account.offline": { "en": "Read Signal offline", diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index 179932844..6b845e094 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -6,7 +6,8 @@ mod trust_mode; use crate::{Account, DEFAULT_DEVICE_NAME, DEFAULT_HOST_NAME}; use group_permission::GroupPermission; use link_state::LinkState; -use std::io::Error; +use locales::t; +use modals::Modals; use signal_ws::SignalWS; use std::io::{Error, ErrorKind}; pub use trust_mode::TrustMode; @@ -144,8 +145,30 @@ impl Manager { match ws.read() { Ok(Message::Binary(uuid)) => { log::info!("raw uuid ProtoBuffer: {:?}", uuid); - ws.close(); - Ok(false) + let uuid = "TODO decode uuid ProtoBuffer"; + let pub_key = "TODO generateIdentityKeyPair()"; + match url::Url::parse_with_params( + "sgnl://linkdevice", + &[("uuid", &uuid), ("pub_key", &pub_key)], + ) { + Ok(device_link_uri) => { + log::info!("device_link_uri: {device_link_uri}"); + let xns = xous_names::XousNames::new().unwrap(); + let modals = + Modals::new(&xns).expect("can't connect to Modals server"); + modals + .show_notification( + t!("sigchat.account.link.scan", locales::LANG), + Some(device_link_uri.as_str()), + ) + .expect("qrcode failed"); + Ok(false) + } + Err(e) => { + log::info!("{}", format!("{e}")); + Err(link_err) + } + } } Ok(_) => { log::warn!("unexpected Provisioning msg."); From 3dff72b2b3f8814d7bed0a946bd76a68b146bedc Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 19 Dec 2023 19:36:13 +1100 Subject: [PATCH 22/49] sigchat obtain device registration from Signal Provisioning server --- apps/sigchat/src/manager.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index 6b845e094..c0727cf3c 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -162,7 +162,21 @@ impl Manager { Some(device_link_uri.as_str()), ) .expect("qrcode failed"); - Ok(false) + match ws.read() { + Ok(Message::Binary(registration)) => { + log::info!("raw registration ProtoBuffer: {:?}", registration); + ws.close(); + Ok(true) + } + Ok(_) => { + log::warn!("unexpected Provisioning msg"); + Err(link_err) + } + Err(e) => { + log::warn!("{e}"); + Err(link_err) + } + } } Err(e) => { log::info!("{}", format!("{e}")); From 728da47c789c7095d6a435a126d5c2197a092594 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Wed, 20 Dec 2023 05:14:25 +1100 Subject: [PATCH 23/49] add sigchat Manager doco --- apps/sigchat/src/manager.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index c0727cf3c..8b1084051 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -129,6 +129,15 @@ impl Manager { /// Link to an existing device, instead of registering a new number. This shows a "sgnl://linkdevice?uuid=…​" URI. If you want to connect to another signal-cli instance, you can just use this URI. If you want to link to an Android/iOS device, create a QR code with the URI (e.g. with qrencode) and scan that in the Signal app. /// + /// Linking sigchat to an existing Signal Account involves: + /// 1. establishing a tls connection to the Signal Provisioning websocket server + /// 2. obtaining a uuid via the websocket + /// 3. generating a key-pair + /// 4. displaying a uri as a qr-code (containing the uuid and pub_key) + /// 5. scanning the qr-code with the primary Signal device + /// 6. obtaining device registration via the websocket + /// 7. completing the registration processing + /// /// # Arguments /// * `name` - Optionally specify a name to describe this new device (defaults to "xous"). /// * `host` - Optionally specify a host to connect to (defaults to "signal.org"). @@ -165,6 +174,7 @@ impl Manager { match ws.read() { Ok(Message::Binary(registration)) => { log::info!("raw registration ProtoBuffer: {:?}", registration); + // TODO complete registration ws.close(); Ok(true) } From 8951a6d72f29051a6d25a482f2d6f9765dd52c23 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Wed, 20 Dec 2023 10:02:58 +1100 Subject: [PATCH 24/49] stub libsignal::generate_identity_key_pair() --- apps/sigchat/src/manager.rs | 6 ++- apps/sigchat/src/manager/libsignal.rs | 57 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 apps/sigchat/src/manager/libsignal.rs diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index 8b1084051..50e9e5575 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -1,4 +1,5 @@ mod group_permission; +mod libsignal; // stub mod link_state; mod signal_ws; mod trust_mode; @@ -155,10 +156,11 @@ impl Manager { Ok(Message::Binary(uuid)) => { log::info!("raw uuid ProtoBuffer: {:?}", uuid); let uuid = "TODO decode uuid ProtoBuffer"; - let pub_key = "TODO generateIdentityKeyPair()"; + let identity_key_pair = libsignal::generate_identity_key_pair(); + let pub_key = identity_key_pair.djb_identity_key.key.clone(); match url::Url::parse_with_params( "sgnl://linkdevice", - &[("uuid", &uuid), ("pub_key", &pub_key)], + &[("uuid", &uuid), ("pub_key", &pub_key.as_str())], ) { Ok(device_link_uri) => { log::info!("device_link_uri: {device_link_uri}"); diff --git a/apps/sigchat/src/manager/libsignal.rs b/apps/sigchat/src/manager/libsignal.rs new file mode 100644 index 000000000..eb53e38f5 --- /dev/null +++ b/apps/sigchat/src/manager/libsignal.rs @@ -0,0 +1,57 @@ +// this is a stub for a wrapped libsignal + + + +// use org.signal.libsignal.protocol.IdentityKey; +pub struct IdentityKey { + pub key:String, +} +impl IdentityKey { + pub fn new(key: String) -> Self { + IdentityKey {key} + } + + pub fn clone(&self) -> IdentityKey { + IdentityKey::new(self.key.clone()) + } +} + +// use org.signal.libsignal.protocol.IdentityKeyPair; +pub struct IdentityKeyPair { + pub djb_identity_key: IdentityKey, + pub djb_private_key: IdentityKey, +} + + +// use org.signal.libsignal.protocol.ecc.Curve; +pub struct Curve {} +impl Curve { + pub fn generate_key_pair() -> DjbKeyPair { + DjbKeyPair { + djb_private_key: IdentityKey::new("STUB privateIdentityKey".to_string()), + djb_public_key: IdentityKey::new("STUB publicIdentityKey".to_string()), + } + } +} + +pub struct DjbKeyPair { + djb_private_key: IdentityKey, + djb_public_key: IdentityKey, +} +impl DjbKeyPair { + pub fn get_private_key(&self) -> IdentityKey { + self.djb_private_key.clone() + } + pub fn get_public_key(&self) -> IdentityKey { + self.djb_public_key.clone() + } +} + + +// https://github.com/AsamK/signal-cli/blob/375bdb79485ec90beb9a154112821a4657740b7a/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java#L45-L51 +pub fn generate_identity_key_pair() -> IdentityKeyPair { + let djb_key_pair = Curve::generate_key_pair(); + let djb_identity_key = IdentityKey::new(djb_key_pair.get_public_key().key); + let djb_private_key = djb_key_pair.get_private_key(); + IdentityKeyPair { djb_identity_key, djb_private_key } +} From 2e2d536dc8e6507a22986f4c5b192f204c66813b Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Wed, 20 Dec 2023 14:36:59 +1100 Subject: [PATCH 25/49] stub libsignal::ProvisioningUuid --- apps/sigchat/src/manager.rs | 5 ++--- apps/sigchat/src/manager/libsignal.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index 50e9e5575..ff678cab8 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -154,13 +154,12 @@ impl Manager { log::info!("provisioning websocket established to {host}"); match ws.read() { Ok(Message::Binary(uuid)) => { - log::info!("raw uuid ProtoBuffer: {:?}", uuid); - let uuid = "TODO decode uuid ProtoBuffer"; + let uuid = libsignal::ProvisioningUuid::decode(uuid).id.clone(); let identity_key_pair = libsignal::generate_identity_key_pair(); let pub_key = identity_key_pair.djb_identity_key.key.clone(); match url::Url::parse_with_params( "sgnl://linkdevice", - &[("uuid", &uuid), ("pub_key", &pub_key.as_str())], + &[("uuid", &uuid.as_str()), ("pub_key", &pub_key.as_str())], ) { Ok(device_link_uri) => { log::info!("device_link_uri: {device_link_uri}"); diff --git a/apps/sigchat/src/manager/libsignal.rs b/apps/sigchat/src/manager/libsignal.rs index eb53e38f5..a8fd77958 100644 --- a/apps/sigchat/src/manager/libsignal.rs +++ b/apps/sigchat/src/manager/libsignal.rs @@ -2,6 +2,23 @@ +// https://github.com/signalapp/Signal-Android/blob/d2053d2db7b1b930b7058ce5506dd6037ac3b808/libsignal-service/src/main/protowire/Provisioning.proto#L13C9-L15 +// +// message ProvisioningUuid { +// optional string uuid = 1; +// } +pub struct ProvisioningUuid { + pub id : String, +} +impl ProvisioningUuid { + pub fn decode(bytes: Vec) -> ProvisioningUuid { + log::info!("raw uuid Protocol Buffer: {:?}", bytes); + ProvisioningUuid {id : "TODO decode uuid Protocol Buffer".to_string()} + } +} + +////////////////////////////////////////////////////////// + // use org.signal.libsignal.protocol.IdentityKey; pub struct IdentityKey { pub key:String, From 0e1c9595e48afdd84dcc71f9b47d695b2824575f Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Wed, 20 Dec 2023 17:19:02 +1100 Subject: [PATCH 26/49] stub libsignal::PrimaryProvisioningCipher.decrypt() --- apps/sigchat/src/manager.rs | 7 ++++++- apps/sigchat/src/manager/libsignal.rs | 30 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index ff678cab8..ec8052858 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -175,8 +175,13 @@ impl Manager { match ws.read() { Ok(Message::Binary(registration)) => { log::info!("raw registration ProtoBuffer: {:?}", registration); - // TODO complete registration + log::info!("device name: {name}"); ws.close(); + let provisioning_message = libsignal::ProvisionMessage::decode(identity_key_pair, registration); + log::info!("provisioning_message.number: {}", provisioning_message.number); + log::info!("provisioning_message.aci: {}", provisioning_message.aci); + log::info!("provisioning_message.pni: {}", provisioning_message.pni); + // TODO complete registration Ok(true) } Ok(_) => { diff --git a/apps/sigchat/src/manager/libsignal.rs b/apps/sigchat/src/manager/libsignal.rs index a8fd77958..b8e2bb663 100644 --- a/apps/sigchat/src/manager/libsignal.rs +++ b/apps/sigchat/src/manager/libsignal.rs @@ -1,6 +1,36 @@ // this is a stub for a wrapped libsignal +// use org.whispersystems.signalservice.internal.crypto.PrimaryProvisioningCipher; +pub struct PrimaryProvisioningCipher{} +impl PrimaryProvisioningCipher { + pub fn new(_stub: Option) -> PrimaryProvisioningCipher { + PrimaryProvisioningCipher {} + } + pub fn decrypt(&self, _temp_identity: IdentityKeyPair, bytes: Vec) -> ProvisionMessage { + //log::info!("temp_identity: {:?}", temp_identity); + log::info!("raw uuid Protocol Buffer: {:?}", bytes); + ProvisionMessage { + number : "STUB number".to_string(), + aci : "STUB number".to_string(), + pni : "STUB number".to_string(), + } + } +} + +pub struct ProvisionMessage { + pub number: String, + pub aci:String, + pub pni:String, +} +impl ProvisionMessage { + pub fn decode(temp_identity: IdentityKeyPair, bytes: Vec) -> ProvisionMessage { + let primary_provisioning_cipher = PrimaryProvisioningCipher::new(None); + primary_provisioning_cipher.decrypt(temp_identity, bytes) + } +} + +//////////////////////////////////////////////////////// // https://github.com/signalapp/Signal-Android/blob/d2053d2db7b1b930b7058ce5506dd6037ac3b808/libsignal-service/src/main/protowire/Provisioning.proto#L13C9-L15 // From de1dcffd6698c16db094ce368115122fa46cdce5 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Wed, 20 Dec 2023 18:44:52 +1100 Subject: [PATCH 27/49] rename shellchat `net tls inspect` rename shellchat `net tls probe` to `net tls inspect` --- libs/tls/locales/i18n.json | 10 +++++----- libs/tls/src/cmd.rs | 14 +++++++------- libs/tls/src/lib.rs | 4 ++++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/libs/tls/locales/i18n.json b/libs/tls/locales/i18n.json index 303ca9028..f8527db37 100644 --- a/libs/tls/locales/i18n.json +++ b/libs/tls/locales/i18n.json @@ -4,8 +4,8 @@ "en-tts": "Do you trust any of these certificate authorities?" }, "tls.cmd_help": { - "en": "Establishing an encrypted tls connection requires the host to provide a signed Certificate of identity. Each Certificate is signed for authenticity by a Certificate Authority. The CA's Certificate will in-turn be signed by yet another CA. These signed Certificates link into a chain of trust back to a trusted Root CA.\n\nTypically, this all happens automagically because many OS's & browsers incorporate a long list of trusted Root CA Certiicates. And this is OK because you trust the CA's that your OS trusts, right!\n\nOn Precursor, you must explicitly trust one or more root CA Certificates. You can:\nnet tls probe \n\tto trust a specific CA Certificate, or\nnet tls mozilla\n\tto trust all of the CA Certificates incporporated in the Firefox browser.", - "en-tts": "Establishing an encrypted tls connection requires the host to provide a signed Certificate of identity. Each Certificate is signed for authenticity by a Certificate Authority. The CA's Certificate will in-turn be signed by yet another CA. These signed Certificates link into a chain of trust back to a trusted Root CA.\n\nTypically, this all happens automagically because many OS's & browsers incorporate a long list of trusted Root CA Certiicates. And this is OK because you trust the CA's that your OS trusts, right!\n\nOn Precursor, you must explicitly trust one or more root CA Certificates. You can:\nnet tls probe \n\tto trust a specific CA Certificate, or\nnet tls mozilla\n\tto trust all of the CA Certificates incporporated in the Firefox browser." + "en": "Establishing an encrypted tls connection requires the host to provide a signed Certificate of identity. Each Certificate is signed for authenticity by a Certificate Authority. The CA's Certificate will in-turn be signed by yet another CA. These signed Certificates link into a chain of trust back to a trusted Root CA.\n\nTypically, this all happens automagically because many OS's & browsers incorporate a long list of trusted Root CA Certiicates. And this is OK because you trust the CA's that your OS trusts, right!\n\nOn Precursor, you must explicitly trust one or more root CA Certificates. You can:\nnet tls inspect \n\tto trust a specific CA Certificate, or\nnet tls mozilla\n\tto trust all of the CA Certificates incporporated in the Firefox browser.", + "en-tts": "Establishing an encrypted tls connection requires the host to provide a signed Certificate of identity. Each Certificate is signed for authenticity by a Certificate Authority. The CA's Certificate will in-turn be signed by yet another CA. These signed Certificates link into a chain of trust back to a trusted Root CA.\n\nTypically, this all happens automagically because many OS's & browsers incorporate a long list of trusted Root CA Certiicates. And this is OK because you trust the CA's that your OS trusts, right!\n\nOn Precursor, you must explicitly trust one or more root CA Certificates. You can:\nnet tls inspect \n\tto trust a specific CA Certificate, or\nnet tls mozilla\n\tto trust all of the CA Certificates incporporated in the Firefox browser." }, "tls.cmd": { "en": "net tls ", @@ -35,11 +35,11 @@ "en": "trusted Root CA's", "en-tts": "trusted Root CA's" }, - "tls.probe_cmd": { + "tls.inspect_cmd": { "en": "save host CA'a if trusted", "en-tts": "save host CA'a if trusted" }, - "tls.probe_fail_servername": { + "tls.inspect_fail_servername": { "en": "failed to create server name from", "en-tts": "failed to create server name from" }, @@ -55,7 +55,7 @@ "en": "error maybe caused by an improperly set clock", "en-tts": "error maybe caused by an improperly set clock" }, - "tls.probe_done": { + "tls.inspect_done": { "en": "trusted CA's", "en-tts": "trusted CA's" }, diff --git a/libs/tls/src/cmd.rs b/libs/tls/src/cmd.rs index 9df851149..78f2e67c4 100644 --- a/libs/tls/src/cmd.rs +++ b/libs/tls/src/cmd.rs @@ -69,23 +69,23 @@ pub fn shellchat<'a>( modals.finish_progress().expect("finish progress"); write!(ret, "{} {}", count, t!("tls.mozilla_done", locales::LANG)).ok(); } - // probe establishes a tls connection to the supplied host, extracts the + // inspect establishes a tls connection to the supplied host, extracts the // certificates offered and immediately closes the connection. // The certificates are presented by modal to the user, and saved to the // pddb if trusted. - Some("probe") => { + Some("inspect") => { log::set_max_level(log::LevelFilter::Info); let target = match tokens.next() { Some(target) => target, None => "betrusted.io", }; let tls = Tls::new(); - match tls.probe(target) { - Ok(count) => write!(ret, "{} {}", count, t!("tls.probe_done", locales::LANG)).ok(), + match tls.inspect(target) { + Ok(count) => write!(ret, "{} {}", count, t!("tls.inspect_done", locales::LANG)).ok(), Err(_) => write!( ret, "{} {target}", - t!("tls.probe_fail_servername", locales::LANG) + t!("tls.inspect_fail_servername", locales::LANG) ) .ok(), }; @@ -170,8 +170,8 @@ pub fn shellchat<'a>( write!(ret, "\tmozilla\t{}\n", t!("tls.mozilla_cmd", locales::LANG)).ok(); write!( ret, - "\tprobe \t{}\n", - t!("tls.probe_cmd", locales::LANG) + "\tinspect \t{}\n", + t!("tls.inspect_cmd", locales::LANG) ) .ok(); write!( diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index 9b47719ab..2f7570c03 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -336,6 +336,10 @@ impl Tls { } } + pub fn inspect(&self, target: &str) -> Result { + self.probe(target) + } + pub fn client_config(&self) -> ClientConfig { rustls::ClientConfig::builder() .with_safe_defaults() From 12c74e724fc90c5743ff12cfbc12c516975a76b6 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 21 Dec 2023 06:51:54 +1100 Subject: [PATCH 28/49] implement Tls.inspect(host) Tls.probe() now returns a Vec and Tls.inspect(host) presents the trust_modal to the user --- apps/sigchat/src/lib.rs | 21 ++++++++++--------- libs/tls/src/lib.rs | 46 +++++++++++++++++++++++++++-------------- libs/tls/src/xtls.rs | 8 +++---- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index db37bb433..03f8d1160 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -178,16 +178,17 @@ impl<'a> SigChat<'a> { self.chat.set_busy_state(true); let tls = Tls::new(); match tls.probe(host) { - Ok(0) => { - self.modals - .show_notification(t!("sigchat.account.abort", locales::LANG), None) - .expect("abort failed"); - self.chat.set_busy_state(false); - false - } - Ok(_count) => { - self.chat.set_busy_state(false); - true + Ok(certs) => { + if certs.len() == 0 { + self.modals + .show_notification(t!("sigchat.account.abort", locales::LANG), None) + .expect("abort failed"); + self.chat.set_busy_state(false); + false + } else { + self.chat.set_busy_state(false); + true + } } Err(e) => { self.chat.set_busy_state(false); diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index 2f7570c03..84f79370e 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -45,7 +45,7 @@ impl Tls { /// /// a count of trusted certificates /// - pub fn trust_modal(&self, certificates: &[Certificate]) -> usize { + pub fn trust_modal(&self, certificates: Vec) -> usize { let xns = XousNames::new().unwrap(); let modals = Modals::new(&xns).unwrap(); @@ -278,12 +278,10 @@ impl Tls { root_store } - /// Probes the target and returns a count of newly trusted 1 certificates + /// Probes the host and returns the TLS chain of trust /// - /// Establishes a tls connection to the target host, extracts the + /// Establishes a tls connection to the host, extracts the /// certificates offered and immediately closes the connection. - /// The certificates are presented by modal to the user, and saved to the - /// pddb if trusted. /// /// By default, rustls only provides access to a trusted certificate chain. /// Probe briefly stifles the certificate validation (ie trusts everything) @@ -291,10 +289,12 @@ impl Tls { /// user for examination. /// /// # Arguments + /// * `host` - the target tls site (i.e. betrusted.io) /// - /// * `target` - the target tls site (i.e. betrusted.io) + /// # Returns + /// The TLS chain of trust for the host /// - pub fn probe(&self, target: &str) -> Result { + pub fn probe(&self, host: &str) -> Result, Error> { log::info!("starting TLS probe"); // Attempt to open the tls connection with an empty root_store let root_store = rustls::RootCertStore::empty(); @@ -306,12 +306,12 @@ impl Tls { .with_safe_defaults() .with_custom_certificate_verifier(stifled_verifier) .with_no_client_auth(); - match target.try_into() { + match host.try_into() { Ok(server_name) => { let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); - log::info!("connect TCPstream to {}", target); - match TcpStream::connect((target, 443)) { + log::info!("connect TCPstream to {}", host); + match TcpStream::connect((host, 443)) { Ok(mut sock) => { match conn.complete_io(&mut sock) { Ok(_) => log::info!("handshake complete"), @@ -319,8 +319,8 @@ impl Tls { } conn.send_close_notify(); match conn.peer_certificates() { - Some(certificates) => Ok(self.trust_modal(certificates)), - None => Ok(0), + Some(certificates) => Ok(certificates.to_vec()), + None => Ok(vec![]), } } Err(e) => { @@ -330,14 +330,30 @@ impl Tls { } } Err(e) => { - log::warn!("failed to create sever_name from {target}: {e}"); + log::warn!("failed to create sever_name from {host}: {e}"); Err(Error::from(ErrorKind::InvalidInput)) } } } - pub fn inspect(&self, target: &str) -> Result { - self.probe(target) + /// Inspect and optionally trust Certificates offered by the host. + /// + /// Probes the host and presents the certificates offered in a modal. + /// The user can optionally save trusted certificates to the pddb. + /// + /// # Arguments + /// * `host` - the target tls site (i.e. betrusted.io) + /// + /// # Returns the number of trusted Certificates offered by the host + /// + pub fn inspect(&self, host: &str) -> Result { + match self.probe(host) { + Ok(certs) => Ok(self.trust_modal(certs)), + Err(e) => { + log::warn!("failed to probe {host}: {e}"); + Ok(0) + } + } } pub fn client_config(&self) -> ClientConfig { diff --git a/libs/tls/src/xtls.rs b/libs/tls/src/xtls.rs index 53e476f90..7867ad9a9 100644 --- a/libs/tls/src/xtls.rs +++ b/libs/tls/src/xtls.rs @@ -37,8 +37,8 @@ impl ureq::TlsConnector for TlsConnector { if let Some(inner) = e.get_ref() { if let Some(rustls_error) = inner.downcast_ref::() { if let rustls::Error::InvalidCertificate(_) = rustls_error { - if let Ok(n) = tls.probe(dns_name) { - if n > 0 { + if let Ok(certs) = tls.probe(dns_name) { + if certs.len() > 0 { log::info!("try again with new trusted certs"); continue; } @@ -54,8 +54,8 @@ impl ureq::TlsConnector for TlsConnector { } // errors generated early in the tls handshake Err(rustls::Error::InvalidCertificate(_)) => { - if let Ok(n) = tls.probe(dns_name) { - if n > 0 { + if let Ok(certs) = tls.probe(dns_name) { + if certs.len() > 0 { log::info!("try again with new trusted certs"); continue; } From 6e1947d3b013aa26f336fbb00e403abaa1af4f20 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 21 Dec 2023 09:20:51 +1100 Subject: [PATCH 29/49] rename Tls.*_cert() to Tls.*_rota() actually this is a correction of the names of these fn these fn are actually handling trust_anchors rather than certificates and in some cases we will need equivalent fn for certificates --- libs/tls/src/cmd.rs | 4 ++-- libs/tls/src/lib.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libs/tls/src/cmd.rs b/libs/tls/src/cmd.rs index 78f2e67c4..03883eeae 100644 --- a/libs/tls/src/cmd.rs +++ b/libs/tls/src/cmd.rs @@ -21,7 +21,7 @@ pub fn shellchat<'a>( Some("deleteall") => { log::info!("starting TLS delete certificates"); let tls = Tls::new(); - let count = tls.del_all_cert().unwrap(); + let count = tls.del_all_rota().unwrap(); write!(ret, "{} {}", count, t!("tls.deleteall_done", locales::LANG)).ok(); log::info!("finished TLS delete certificates"); } @@ -62,7 +62,7 @@ pub fn shellchat<'a>( count = 0; let tls = Tls::new(); for rota in rotas { - tls.save_cert(&rota).unwrap_or_else(|e| log::warn!("{e}")); + tls.save_rota(&rota).unwrap_or_else(|e| log::warn!("{e}")); modals.update_progress(count).expect("no progress"); count += 1; } diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index 84f79370e..db191cf7b 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -84,7 +84,7 @@ impl Tls { .map(|i| &certificates[*i].1) .map(|x509| RustlsOwnedTrustAnchor::from(x509)) .for_each(|rota| { - self.save_cert(&rota).unwrap_or_else(|e| { + self.save_rota(&rota).unwrap_or_else(|e| { log::warn!("failed to save cert: {e}"); modals .show_notification( @@ -109,7 +109,7 @@ impl Tls { /// /// the number of trust-anchors deleted /// - pub fn del_all_cert(&self) -> Result { + pub fn del_all_rota(&self) -> Result { let count = match self.pddb.list_keys(TLS_TRUSTED_DICT, None) { Ok(list) => list.len(), Err(_) => 0, @@ -137,7 +137,7 @@ impl Tls { /// /// * `key` - the pddb-key containing the unwanted trust-anchor /// - pub fn del_cert(&self, key: &str) -> Result<(), Error> { + pub fn del_rota(&self, key: &str) -> Result<(), Error> { match self.pddb.delete_key(TLS_TRUSTED_DICT, key, None) { Ok(_) => { log::info!("Deleted {}:{}\n", TLS_TRUSTED_DICT, key); @@ -157,7 +157,7 @@ impl Tls { /// /// * `ta` - a trusted trust-anchor /// - pub fn save_cert(&self, ta: &RustlsOwnedTrustAnchor) -> Result<(), Error> { + pub fn save_rota(&self, ta: &RustlsOwnedTrustAnchor) -> Result<(), Error> { let key = ta.pddb_key(); match self.pddb.get( TLS_TRUSTED_DICT, @@ -206,7 +206,7 @@ impl Tls { /// /// * `key` - pddb key holding the trust-anchor /// - pub fn get_cert(&self, key: &str) -> Option { + pub fn get_rota(&self, key: &str) -> Option { match self.pddb.get( TLS_TRUSTED_DICT, key, @@ -250,7 +250,7 @@ impl Tls { match self.pddb.list_keys(TLS_TRUSTED_DICT, None) { Ok(list) => list .iter() - .map(|key| self.get_cert(&key)) + .map(|key| self.get_rota(&key)) .filter_map(|rota| rota) .collect::>(), Err(e) => { @@ -268,7 +268,7 @@ impl Tls { Ok(list) => { let rota = list .iter() - .map(|key| self.get_cert(&key)) + .map(|key| self.get_rota(&key)) .filter_map(|rota| rota) .map(|t| Into::::into(t)); root_store.add_trust_anchors(rota); From 0ca24db2f0846e7605d343c570c2abb384d03d92 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 21 Dec 2023 17:52:52 +1100 Subject: [PATCH 30/49] implement Tls.is_trusted_cert() & Tls.is_trusted_x509() --- libs/tls/src/lib.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index db191cf7b..c6ad1e0f3 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -260,6 +260,41 @@ impl Tls { } } + pub fn is_trusted_cert(&self, cert: Certificate) -> bool { + match X509Certificate::from_der(cert.as_ref()) { + Ok(result) => self.is_trusted_x509(&result.1), + Err(e) => { + log::warn!("failed to get x509 from Certificate: {e}"); + false + } + } + } + + pub fn is_trusted_x509(&self, x509: &X509Certificate) -> bool { + let ta = RustlsOwnedTrustAnchor::from(x509); + let key = ta.pddb_key(); + match self.pddb.get( + TLS_TRUSTED_DICT, + &key, + None, + false, + false, + None, + None::, + ) { + // Ok(_) => true, + // Err(_) => false, + Ok(_) => { + log::info!("trusted: {key}"); + true + } + Err(_) => { + log::info!("UNtrusted: {key}"); + false + } + } + } + /// Returns a RootCertStore containing all trusted trust-anchors /// pub fn root_store(&self) -> RootCertStore { From 7d72387cb908d0c9369acaa6518d8064e015b147 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Fri, 22 Dec 2023 14:24:08 +1100 Subject: [PATCH 31/49] simplify TLS.probe() modals --- libs/tls/locales/i18n.json | 8 ++------ libs/tls/src/danger.rs | 34 ++++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/libs/tls/locales/i18n.json b/libs/tls/locales/i18n.json index f8527db37..5ed746284 100644 --- a/libs/tls/locales/i18n.json +++ b/libs/tls/locales/i18n.json @@ -1,7 +1,7 @@ { "tls.check_trust_prompt": { - "en": "Do you trust any of these certificate authorities?", - "en-tts": "Do you trust any of these certificate authorities?" + "en": "Please select trusted certificate authorities.", + "en-tts": "Please select trusted certificate authorities." }, "tls.cmd_help": { "en": "Establishing an encrypted tls connection requires the host to provide a signed Certificate of identity. Each Certificate is signed for authenticity by a Certificate Authority. The CA's Certificate will in-turn be signed by yet another CA. These signed Certificates link into a chain of trust back to a trusted Root CA.\n\nTypically, this all happens automagically because many OS's & browsers incorporate a long list of trusted Root CA Certiicates. And this is OK because you trust the CA's that your OS trusts, right!\n\nOn Precursor, you must explicitly trust one or more root CA Certificates. You can:\nnet tls inspect \n\tto trust a specific CA Certificate, or\nnet tls mozilla\n\tto trust all of the CA Certificates incporporated in the Firefox browser.", @@ -47,10 +47,6 @@ "en": "Invalid certificate : ", "en-tts": "Invalid certificate : " }, - "tls.probe_help_unknown_issuer": { - "en": "you may choose to trust a known CA", - "en-tts": "you may choose to trust a known CA" - }, "tls.probe_help_not_valid_yet": { "en": "error maybe caused by an improperly set clock", "en-tts": "error maybe caused by an improperly set clock" diff --git a/libs/tls/src/danger.rs b/libs/tls/src/danger.rs index f47acf11c..618c97526 100644 --- a/libs/tls/src/danger.rs +++ b/libs/tls/src/danger.rs @@ -34,26 +34,32 @@ impl rustls::client::ServerCertVerifier for StifledCertificateVerification { Err(Error::InvalidCertificate(e)) => { let xns = XousNames::new().unwrap(); let modals = Modals::new(&xns).unwrap(); - modals - .show_notification( - format!("{}\n{:?}", t!("tls.probe_invalid_certificate", locales::LANG), e).as_str(), - None, - ) - .expect("modal failed"); match e { - CertificateError::UnknownIssuer => { - modals - .show_notification(t!("tls.probe_help_unknown_issuer", locales::LANG), None) - .expect("modal failed"); - Ok(ServerCertVerified::assertion()) - } + CertificateError::UnknownIssuer => Ok(ServerCertVerified::assertion()), CertificateError::NotValidYet => { modals - .show_notification(t!("tls.probe_help_not_valid_yet", locales::LANG), None) + .show_notification( + t!("tls.probe_help_not_valid_yet", locales::LANG), + None, + ) .expect("modal failed"); Err(Error::InvalidCertificate(e)) } - _ => Err(Error::InvalidCertificate(e)), + _ => { + modals + .show_notification( + format!( + "{}\n{:?}", + t!("tls.probe_invalid_certificate", locales::LANG), + e + ) + .as_str(), + None, + ) + .expect("modal failed"); + + Err(Error::InvalidCertificate(e)) + } } } Err(e) => Err(e), From 5703707ccacafb096e7de22e948ce5104e1e0dbd Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Fri, 22 Dec 2023 14:25:51 +1100 Subject: [PATCH 32/49] implement Tls.accessible() --- libs/tls/src/lib.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index c6ad1e0f3..ea25d45e7 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -391,6 +391,37 @@ impl Tls { } } + /// Check if host offers a trusted Certificate, and optionally inspect if none trusted. + /// + /// Probes the host and checks if any of the Certificates are trusted. If none are + /// trusted, and inspect==true, then the offered certificates are presented in a modal. + /// The user can optionally save trusted certificates to the pddb. + /// + /// # Arguments + /// * `host` - the target tls site (i.e. betrusted.io) + /// * `inspect` - if no trusted certificates then inspect those offered + /// + /// # Returns + /// true if the user trusts at least one of the Certificates offered by the host. + /// + pub fn accessible(&self, host: &str, inspect: bool) -> bool { + match self.probe(host) { + Ok(certs) => { + match certs + .iter() + .find(|&cert| self.is_trusted_cert(cert.clone())) + { + Some(_) => true, + None => inspect && (self.trust_modal(certs) > 0), + } + } + Err(e) => { + log::warn!("failed to probe {host}: {e}"); + false + } + } + } + pub fn client_config(&self) -> ClientConfig { rustls::ClientConfig::builder() .with_safe_defaults() From 5e1c6f627695d9d6bc49494e63a187fd5a9c7ac9 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Fri, 22 Dec 2023 14:29:06 +1100 Subject: [PATCH 33/49] re-implement Sigchat.probe_host() using Tls.accessible() now the user is only presented with the tls trust_modal when none of the required CA's are trusted --- apps/sigchat/src/lib.rs | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index 03f8d1160..ad7c6b37d 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -177,26 +177,15 @@ impl<'a> SigChat<'a> { .set_status_text(t!("sigchat.status.probing", locales::LANG)); self.chat.set_busy_state(true); let tls = Tls::new(); - match tls.probe(host) { - Ok(certs) => { - if certs.len() == 0 { - self.modals - .show_notification(t!("sigchat.account.abort", locales::LANG), None) - .expect("abort failed"); - self.chat.set_busy_state(false); - false - } else { - self.chat.set_busy_state(false); - true - } - } - Err(e) => { - self.chat.set_busy_state(false); - self.modals - .show_notification(&format!("{}", e), None) - .expect("qrcode failed"); - false - } + if tls.accessible(host, true) { + self.chat.set_busy_state(false); + true + } else { + self.modals + .show_notification(t!("sigchat.account.abort", locales::LANG), None) + .expect("abort failed"); + self.chat.set_busy_state(false); + false } } From 854b2198c49e963bb9d86e236191f5f260cfd602 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Sun, 24 Dec 2023 06:03:26 +1100 Subject: [PATCH 34/49] tls doc & tidy-up --- libs/tls/src/cmd.rs | 4 +++- libs/tls/src/lib.rs | 54 +++++++++++++++++++++++++++++++++++++++----- libs/tls/src/rota.rs | 4 +--- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/libs/tls/src/cmd.rs b/libs/tls/src/cmd.rs index 03883eeae..85d22a91b 100644 --- a/libs/tls/src/cmd.rs +++ b/libs/tls/src/cmd.rs @@ -81,7 +81,9 @@ pub fn shellchat<'a>( }; let tls = Tls::new(); match tls.inspect(target) { - Ok(count) => write!(ret, "{} {}", count, t!("tls.inspect_done", locales::LANG)).ok(), + Ok(count) => { + write!(ret, "{} {}", count, t!("tls.inspect_done", locales::LANG)).ok() + } Err(_) => write!( ret, "{} {target}", diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index ea25d45e7..b9a03b5a3 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -260,6 +260,16 @@ impl Tls { } } + /// Checks if the rustls Certificate provided is trusted (saved in pddb) + /// + /// # Arguments + /// + /// * `cert` - an rustls Certificate to be checked + /// + /// # Returns + /// + /// true if the certificate is saved in the TLS_TRUSTED_DICT in the pddb + /// pub fn is_trusted_cert(&self, cert: Certificate) -> bool { match X509Certificate::from_der(cert.as_ref()) { Ok(result) => self.is_trusted_x509(&result.1), @@ -270,6 +280,16 @@ impl Tls { } } + /// Checks if the x509 Certificate provided is trusted (saved in pddb) + /// + /// $ Arguments + /// + /// * `x509` - an x509 Certificate to be checked + /// + /// # Returns + /// + /// true if the certificate is saved in the TLS_TRUSTED_DICT in the pddb + /// pub fn is_trusted_x509(&self, x509: &X509Certificate) -> bool { let ta = RustlsOwnedTrustAnchor::from(x509); let key = ta.pddb_key(); @@ -282,8 +302,6 @@ impl Tls { None, None::, ) { - // Ok(_) => true, - // Err(_) => false, Ok(_) => { log::info!("trusted: {key}"); true @@ -324,9 +342,11 @@ impl Tls { /// user for examination. /// /// # Arguments + /// /// * `host` - the target tls site (i.e. betrusted.io) /// /// # Returns + /// /// The TLS chain of trust for the host /// pub fn probe(&self, host: &str) -> Result, Error> { @@ -377,9 +397,12 @@ impl Tls { /// The user can optionally save trusted certificates to the pddb. /// /// # Arguments + /// /// * `host` - the target tls site (i.e. betrusted.io) /// - /// # Returns the number of trusted Certificates offered by the host + /// # Returns + /// + /// the number of trusted Certificates offered by the host /// pub fn inspect(&self, host: &str) -> Result { match self.probe(host) { @@ -398,10 +421,12 @@ impl Tls { /// The user can optionally save trusted certificates to the pddb. /// /// # Arguments + /// /// * `host` - the target tls site (i.e. betrusted.io) /// * `inspect` - if no trusted certificates then inspect those offered /// /// # Returns + /// /// true if the user trusts at least one of the Certificates offered by the host. /// pub fn accessible(&self, host: &str, inspect: bool) -> bool { @@ -429,14 +454,31 @@ impl Tls { .with_no_client_auth() } - pub fn stream_owned(&self, host: &str, sock: TcpStream) -> Result, Error> { + /// Construct a tls-stream on the tcp-stream provided + /// + /// # Arguments + /// + /// * `host` - the host end-point of the stream + /// * `sock` - a tcp-stream connected to host + /// + /// # Returns + /// + /// an owned rusttls stream on the tcp-stream provided + pub fn stream_owned( + &self, + host: &str, + sock: TcpStream, + ) -> Result, Error> { match rustls::ClientConnection::new( Arc::new(self.client_config()), host.try_into().expect("failed url host_str"), ) { Ok(conn) => Ok(rustls::StreamOwned::new(conn, sock)), - Err(_) => Err(Error::new(ErrorKind::Other, "failed to configure client connection")) - } + Err(_) => Err(Error::new( + ErrorKind::Other, + "failed to configure client connection", + )), + } } } diff --git a/libs/tls/src/rota.rs b/libs/tls/src/rota.rs index 31bff68f8..d63347df8 100644 --- a/libs/tls/src/rota.rs +++ b/libs/tls/src/rota.rs @@ -142,9 +142,7 @@ fn add_der_header(tag: Tag, naked: &Vec) -> Result, Error> { Ok(header) => { let mut buff: [u8; 32] = [0u8; 32]; match header.encode_to_slice(&mut buff) { - Ok(der) => { - Ok([der, naked].concat()) - } + Ok(der) => Ok([der, naked].concat()), Err(_) => Err(Error::new( ErrorKind::InvalidData, "der parse failed: encode", From 4a824c903e236318af22f2b72ba09b532004db88 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 26 Dec 2023 06:49:33 +1100 Subject: [PATCH 35/49] add sigchat Account fields --- apps/sigchat/src/account.rs | 173 +++++++++++++++++++++++++++++++++--- 1 file changed, 162 insertions(+), 11 deletions(-) diff --git a/apps/sigchat/src/account.rs b/apps/sigchat/src/account.rs index 1a301969b..31e20ace2 100644 --- a/apps/sigchat/src/account.rs +++ b/apps/sigchat/src/account.rs @@ -9,48 +9,199 @@ use std::str::FromStr; pub struct Account { pddb: Pddb, pddb_dict: String, - service_environment: ServiceEnvironment, + aci_identity_private: Option, + aci_identity_public: Option, + aci_service_id: Option, + device_id: u32, + encrypted_device_name: Option, + is_multi_device: bool, number: Option, + password: Option, + pin_master_key: Option, + pni_identity_private: Option, + pni_identity_public: Option, + pni_service_id: Option, + profile_key: Option, registered: bool, + service_environment: ServiceEnvironment, + storage_key: Option, + store_last_receive_timestamp: i64, + store_manifest_version: i64, + store_manifest: Option, } -const SERVICE_ENVIRONMENT_KEY: &str = "service_environment"; +const ACI_IDENTITY_PRIVATE_KEY: &str = "aci.identity.private"; +const ACI_IDENTITY_PUBLIC_KEY: &str = "aci.identity.public"; +const ACI_SERVICE_ID_KEY: &str = "aci.service_id"; +const DEVICE_ID_KEY: &str = "device_id"; +const ENCRYPTED_DEVICE_NAME_KEY: &str = "encrypted_device_name"; +const IS_MULTI_DEVICE_KEY: &str = "is_multi_device"; const NUMBER_KEY: &str = "number"; +const PASSWORD_KEY: &str = "password"; +const PIN_MASTER_KEY_KEY: &str = "pin_master_key"; +const PNI_IDENTITY_PRIVATE_KEY: &str = "pni.identity.private"; +const PNI_IDENTITY_PUBLIC_KEY: &str = "pni.identity.public"; +const PNI_SERVICE_ID_KEY: &str = "pni.service_id"; +const PROFILE_KEY_KEY: &str = "profile_key"; const REGISTERED_KEY: &str = "registered"; +const SERVICE_ENVIRONMENT_KEY: &str = "service_environment"; +const STORAGE_KEY_KEY: &str = "storage_key"; +const STORE_LAST_RECEIVE_TIMESTAMP_KEY: &str = "store_last_receive_timestamp"; +const STORE_MANIFEST_VERSION_KEY: &str = "store_manifest_version"; +const STORE_MANIFEST_KEY: &str = "store_manifest"; impl Account { + // Create a new Account stored in pddb with default values + // + // This function saves default values for each field in the pddb + // and then calls read() to load the values into the Account struct + // + // # Arguments + // * `pddb_dict` - the pddb dictionary name to hold the Account + // + // # Returns + // + // a new Account with default values + // pub fn new(pddb_dict: &str) -> Result { let pddb = pddb::Pddb::new(); pddb.try_mount(); + set(&pddb, pddb_dict, ACI_IDENTITY_PRIVATE_KEY, None)?; + set(&pddb, pddb_dict, ACI_IDENTITY_PUBLIC_KEY, None)?; + set(&pddb, pddb_dict, ACI_SERVICE_ID_KEY, None)?; + set(&pddb, pddb_dict, DEVICE_ID_KEY, Some("0"))?; + set(&pddb, pddb_dict, ENCRYPTED_DEVICE_NAME_KEY, None)?; + set( + &pddb, + pddb_dict, + IS_MULTI_DEVICE_KEY, + Some(&false.to_string()), + )?; + set(&pddb, pddb_dict, NUMBER_KEY, None)?; + set(&pddb, pddb_dict, PASSWORD_KEY, None)?; + set(&pddb, pddb_dict, PIN_MASTER_KEY_KEY, None)?; + set(&pddb, pddb_dict, PNI_IDENTITY_PRIVATE_KEY, None)?; + set(&pddb, pddb_dict, PNI_IDENTITY_PUBLIC_KEY, None)?; + set(&pddb, pddb_dict, PNI_SERVICE_ID_KEY, None)?; + set(&pddb, pddb_dict, PROFILE_KEY_KEY, None)?; + set(&pddb, pddb_dict, REGISTERED_KEY, Some(&false.to_string()))?; set( &pddb, pddb_dict, SERVICE_ENVIRONMENT_KEY, Some(&ServiceEnvironment::Staging.to_string()), )?; - set(&pddb, pddb_dict, REGISTERED_KEY, Some(&false.to_string()))?; + set(&pddb, pddb_dict, STORAGE_KEY_KEY, None)?; + set( + &pddb, + pddb_dict, + STORE_LAST_RECEIVE_TIMESTAMP_KEY, + Some("0"), + )?; + set(&pddb, pddb_dict, STORE_MANIFEST_VERSION_KEY, Some("-1"))?; + set(&pddb, pddb_dict, STORE_MANIFEST_KEY, None)?; Account::read(pddb_dict) } + + // retrieves an existing Account from the pddb + // + // # Arguments + // * `pddb_dict` - the pddb dictionary name holding the Account + // + // # Returns + // + // a Account with values read from pddb_dict + // pub fn read(pddb_dict: &str) -> Result { let pddb = pddb::Pddb::new(); pddb.try_mount(); match ( - get(&pddb, pddb_dict, SERVICE_ENVIRONMENT_KEY), - get(&pddb, pddb_dict, REGISTERED_KEY), + get(&pddb, pddb_dict, ACI_IDENTITY_PRIVATE_KEY), + get(&pddb, pddb_dict, ACI_IDENTITY_PUBLIC_KEY), + get(&pddb, pddb_dict, ACI_SERVICE_ID_KEY), + get(&pddb, pddb_dict, DEVICE_ID_KEY), + get(&pddb, pddb_dict, ENCRYPTED_DEVICE_NAME_KEY), + get(&pddb, pddb_dict, IS_MULTI_DEVICE_KEY), get(&pddb, pddb_dict, NUMBER_KEY), + get(&pddb, pddb_dict, PASSWORD_KEY), + get(&pddb, pddb_dict, PIN_MASTER_KEY_KEY), + get(&pddb, pddb_dict, PNI_IDENTITY_PRIVATE_KEY), + get(&pddb, pddb_dict, PNI_IDENTITY_PUBLIC_KEY), + get(&pddb, pddb_dict, PNI_SERVICE_ID_KEY), + get(&pddb, pddb_dict, PROFILE_KEY_KEY), + get(&pddb, pddb_dict, REGISTERED_KEY), + get(&pddb, pddb_dict, SERVICE_ENVIRONMENT_KEY), + get(&pddb, pddb_dict, STORAGE_KEY_KEY), + get(&pddb, pddb_dict, STORE_LAST_RECEIVE_TIMESTAMP_KEY), + get(&pddb, pddb_dict, STORE_MANIFEST_VERSION_KEY), + get(&pddb, pddb_dict, STORE_MANIFEST_KEY), ) { - (Ok(Some(service_environment)), Ok(Some(registered)), Ok(number)) => Ok(Account { + ( + Ok(aci_identity_private), + Ok(aci_identity_public), + Ok(aci_service_id), + Ok(Some(device_id)), + Ok(encrypted_device_name), + Ok(Some(is_multi_device)), + Ok(number), + Ok(password), + Ok(pin_master_key), + Ok(pni_identity_private), + Ok(pni_identity_public), + Ok(pni_service_id), + Ok(profile_key), + Ok(Some(registered)), + Ok(Some(service_environment)), + Ok(storage_key), + Ok(Some(store_last_receive_timestamp)), + Ok(Some(store_manifest_version)), + Ok(store_manifest), + ) => Ok(Account { pddb: pddb, pddb_dict: pddb_dict.to_string(), - service_environment: ServiceEnvironment::from_str(&service_environment).unwrap(), + aci_identity_private: aci_identity_private, + aci_identity_public: aci_identity_public, + aci_service_id: aci_service_id, + device_id: device_id.parse().unwrap(), + encrypted_device_name: encrypted_device_name, + is_multi_device: is_multi_device.parse().unwrap(), number: number, + password: password, + pin_master_key: pin_master_key, + pni_identity_private: pni_identity_private, + pni_identity_public: pni_identity_public, + pni_service_id: pni_service_id, + profile_key: profile_key, registered: registered.parse().unwrap(), + service_environment: ServiceEnvironment::from_str(&service_environment).unwrap(), + storage_key: storage_key, + store_last_receive_timestamp: store_last_receive_timestamp.parse().unwrap(), + store_manifest_version: store_manifest_version.parse().unwrap(), + store_manifest: store_manifest, }), - (Err(e), _, _) => Err(e), - (_, Err(e), _) => Err(e), - (_, _, Err(e)) => Err(e), - (_, _, _) => Err(Error::from(ErrorKind::InvalidData)), + (Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e)) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => { + Err(Error::from(ErrorKind::InvalidData)) + } } } From 68b2c55460458d47c01702b87d18037640ec1ebe Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 26 Dec 2023 07:10:09 +1100 Subject: [PATCH 36/49] refine sigchat Account.set() to ensure consistency between pddb & struct --- apps/sigchat/src/account.rs | 47 ++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/apps/sigchat/src/account.rs b/apps/sigchat/src/account.rs index 31e20ace2..0549bb68e 100644 --- a/apps/sigchat/src/account.rs +++ b/apps/sigchat/src/account.rs @@ -239,9 +239,50 @@ impl Account { get(&self.pddb, &self.pddb_dict, key) } - #[allow(dead_code)] - fn set(&self, key: &str, value: Option<&str>) -> Result<(), Error> { - set(&self.pddb, &self.pddb_dict, key, value) + + // Sets the value of a pddb_key / field in the Account + // + // To guarantee consistency, the value is saved to the pddb and, + // on success, set to the corresponding field in the Account struct. + // + // # Arguments + // * `key` - the pddb_key corresponding to the Account field + // * `value` - the value to save into the Account field (and pddb) + // + // # Returns + // + // Ok() + // + fn set(&mut self, key: &str, value: Option<&str>) -> Result<(), Error> { + let owned_value = value.map(str::to_string); + match set(&self.pddb, &self.pddb_dict, key, value) { + Ok(()) => match key { + ACI_IDENTITY_PRIVATE_KEY => Ok(self.aci_identity_private = owned_value), + ACI_IDENTITY_PUBLIC_KEY => Ok(self.aci_identity_public = owned_value), + ACI_SERVICE_ID_KEY => Ok(self.aci_service_id = owned_value), + DEVICE_ID_KEY => Ok(self.device_id = owned_value.unwrap().parse().unwrap()), + IS_MULTI_DEVICE_KEY => { + Ok(self.is_multi_device = owned_value.unwrap().parse().unwrap()) + } + NUMBER_KEY => Ok(self.number = owned_value), + PASSWORD_KEY => Ok(self.password = owned_value), + PIN_MASTER_KEY_KEY => Ok(self.pin_master_key = owned_value), + PNI_IDENTITY_PRIVATE_KEY => Ok(self.pni_identity_private = owned_value), + PNI_IDENTITY_PUBLIC_KEY => Ok(self.pni_identity_public = owned_value), + PNI_SERVICE_ID_KEY => Ok(self.pni_service_id = owned_value), + PROFILE_KEY_KEY => Ok(self.profile_key = owned_value), + REGISTERED_KEY => Ok(self.registered = owned_value.unwrap().parse().unwrap()), + SERVICE_ENVIRONMENT_KEY => Ok(self.service_environment = + ServiceEnvironment::from_str(&value.unwrap()).unwrap()), + STORAGE_KEY_KEY => Ok(self.storage_key = owned_value), + _ => { + log::warn!("invalid key: {key}"); + let _ = &self.pddb.delete_key(&self.pddb_dict, &key, None); + Err(Error::from(ErrorKind::NotFound)) + } + }, + Err(e) => Err(e), + } } } From 84d5ff4c0c51d047aa61403e4d66f37ad95f0e81 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Tue, 26 Dec 2023 07:17:20 +1100 Subject: [PATCH 37/49] partial implementation of sigchat Account link() --- apps/sigchat/src/account.rs | 61 +++++++++++++++++++++++- apps/sigchat/src/lib.rs | 9 ++-- apps/sigchat/src/manager.rs | 40 +++++++++------- apps/sigchat/src/manager/libsignal.rs | 68 ++++++++++++++++++++------- 4 files changed, 139 insertions(+), 39 deletions(-) diff --git a/apps/sigchat/src/account.rs b/apps/sigchat/src/account.rs index 0549bb68e..a32722ce2 100644 --- a/apps/sigchat/src/account.rs +++ b/apps/sigchat/src/account.rs @@ -1,5 +1,6 @@ mod service_environment; +use crate::manager::libsignal::{DeviceNameUtil, ProvisionMessage}; use pddb::Pddb; use service_environment::ServiceEnvironment; use std::io::{Error, ErrorKind, Read, Write}; @@ -103,7 +104,6 @@ impl Account { Account::read(pddb_dict) } - // retrieves an existing Account from the pddb // // # Arguments @@ -217,6 +217,64 @@ impl Account { Ok(()) } + pub fn link( + &mut self, + device_name: &str, + provisioning_msg: ProvisionMessage, + ) -> Result { + // TODO complete link pre-checks + // Check if this device can be relinked + // check not primary + // check not registered in another environment + // check account state with server + + let aci = provisioning_msg.aci; + self.set(ACI_IDENTITY_PRIVATE_KEY, Some(&aci.djb_private_key.key))?; + self.set(ACI_IDENTITY_PUBLIC_KEY, Some(&aci.djb_identity_key.key))?; + self.set(ACI_SERVICE_ID_KEY, Some(&aci.service_id))?; + self.set(DEVICE_ID_KEY, Some("0"))?; + self.set( + ENCRYPTED_DEVICE_NAME_KEY, + Some(&DeviceNameUtil::encrypt_device_name( + device_name, + aci.djb_private_key, + )), + )?; + self.set(IS_MULTI_DEVICE_KEY, Some(&true.to_string()))?; + self.set(NUMBER_KEY, Some(&provisioning_msg.number))?; + self.set(PASSWORD_KEY, Some("STUB 32 bytes"))?; + self.set(PIN_MASTER_KEY_KEY, Some(&provisioning_msg.master_key))?; + let pni = provisioning_msg.pni; + self.set(PNI_IDENTITY_PRIVATE_KEY, Some(&pni.djb_private_key.key))?; + self.set(PNI_IDENTITY_PUBLIC_KEY, Some(&pni.djb_identity_key.key))?; + self.set(PNI_SERVICE_ID_KEY, Some(&aci.service_id))?; + self.set( + PROFILE_KEY_KEY, + Some( + &provisioning_msg + .profile_key + .unwrap_or_else(|| "STUB 32 bytes".to_string()), + ), + )?; + self.set(REGISTERED_KEY, Some(&false.to_string()))?; + self.set(STORAGE_KEY_KEY, None)?; + self.set(STORE_LAST_RECEIVE_TIMESTAMP_KEY, Some("0"))?; + self.set(STORE_MANIFEST_VERSION_KEY, Some("-1"))?; + self.set(STORE_MANIFEST_KEY, None)?; + + // TODO complete registration setup + // getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress()); + // getSenderKeyStore().deleteAll(); + // trustSelfIdentity(ServiceIdType.ACI); + // trustSelfIdentity(ServiceIdType.PNI); + // aciAccountData.getSessionStore().archiveAllSessions(); + // pniAccountData.getSessionStore().archiveAllSessions(); + // clearAllPreKeys(); + // getKeyValueStore().storeEntry(lastRecipientsRefresh, null); + + Ok(false) + } + #[allow(dead_code)] pub fn number(&self) -> Option<&str> { match &self.number { @@ -239,7 +297,6 @@ impl Account { get(&self.pddb, &self.pddb_dict, key) } - // Sets the value of a pddb_key / field in the Account // // To guarantee consistency, the value is saved to the pddb and, diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index ad7c6b37d..8aa7d81be 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -177,7 +177,7 @@ impl<'a> SigChat<'a> { .set_status_text(t!("sigchat.status.probing", locales::LANG)); self.chat.set_busy_state(true); let tls = Tls::new(); - if tls.accessible(host, true) { + if tls.accessible(host, true) { self.chat.set_busy_state(false); true } else { @@ -236,8 +236,11 @@ impl<'a> SigChat<'a> { } } } - Err(_) => { - log::warn!("failed to create new Account in pddb: {}", SIGCHAT_ACCOUNT); + Err(e) => { + log::warn!( + "failed to create new Account in pddb:{} : {e}", + SIGCHAT_ACCOUNT + ); self.modals .show_notification(t!("sigchat.account.failed", locales::LANG), None) .expect("notification failed"); diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index ec8052858..0d1ce43fd 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -1,5 +1,5 @@ mod group_permission; -mod libsignal; // stub +pub mod libsignal; // stub mod link_state; mod signal_ws; mod trust_mode; @@ -137,7 +137,7 @@ impl Manager { /// 4. displaying a uri as a qr-code (containing the uuid and pub_key) /// 5. scanning the qr-code with the primary Signal device /// 6. obtaining device registration via the websocket - /// 7. completing the registration processing + /// 7. checking and saving account details /// /// # Arguments /// * `name` - Optionally specify a name to describe this new device (defaults to "xous"). @@ -148,12 +148,13 @@ impl Manager { // insert default values as required let name = name.unwrap_or(DEFAULT_DEVICE_NAME); let host = host.unwrap_or(DEFAULT_HOST_NAME); - let link_err = Error::new(ErrorKind::Other, "failed to link device",); + let link_err = Error::new(ErrorKind::Other, "failed to link device"); match SignalWS::new_provision(&host) { Ok(mut ws) => { log::info!("provisioning websocket established to {host}"); match ws.read() { Ok(Message::Binary(uuid)) => { + log::info!("received Provisioning UUID message from host"); let uuid = libsignal::ProvisioningUuid::decode(uuid).id.clone(); let identity_key_pair = libsignal::generate_identity_key_pair(); let pub_key = identity_key_pair.djb_identity_key.key.clone(); @@ -174,21 +175,27 @@ impl Manager { .expect("qrcode failed"); match ws.read() { Ok(Message::Binary(registration)) => { - log::info!("raw registration ProtoBuffer: {:?}", registration); - log::info!("device name: {name}"); + log::info!("Registration message received from host"); ws.close(); - let provisioning_message = libsignal::ProvisionMessage::decode(identity_key_pair, registration); - log::info!("provisioning_message.number: {}", provisioning_message.number); - log::info!("provisioning_message.aci: {}", provisioning_message.aci); - log::info!("provisioning_message.pni: {}", provisioning_message.pni); - // TODO complete registration - Ok(true) + match self.account.link( + name, + libsignal::ProvisionMessage::decode( + identity_key_pair, + registration, + ), + ) { + Ok(result) => Ok(result), + Err(e) => { + log::warn!("linking error: {e}"); + Ok(false) + } + } } - Ok(_) => { + Ok(_) => { log::warn!("unexpected Provisioning msg"); Err(link_err) } - Err(e) => { + Err(e) => { log::warn!("{e}"); Err(link_err) } @@ -200,17 +207,16 @@ impl Manager { } } } - Ok(_) => { + Ok(_) => { log::warn!("unexpected Provisioning msg."); Err(link_err) } - Err(e) => { + Err(e) => { log::warn!("{e}"); Err(link_err) } } - } - + } Err(e) => { log::info!("failed to connect to server: {}", e); Err(e) diff --git a/apps/sigchat/src/manager/libsignal.rs b/apps/sigchat/src/manager/libsignal.rs index b8e2bb663..5b29e753d 100644 --- a/apps/sigchat/src/manager/libsignal.rs +++ b/apps/sigchat/src/manager/libsignal.rs @@ -1,27 +1,56 @@ // this is a stub for a wrapped libsignal +// org.whispersystems.signalservice.api.util.DeviceNameUtil; +pub struct DeviceNameUtil {} +impl DeviceNameUtil { + pub fn encrypt_device_name(_device_name: &str, _aci_private_key: IdentityKey) -> String { + "STUB".to_string() + } +} + +//////////////////////////////////////////////////////// // use org.whispersystems.signalservice.internal.crypto.PrimaryProvisioningCipher; -pub struct PrimaryProvisioningCipher{} +pub struct PrimaryProvisioningCipher {} impl PrimaryProvisioningCipher { - pub fn new(_stub: Option) -> PrimaryProvisioningCipher { - PrimaryProvisioningCipher {} + pub fn new(_stub: Option) -> PrimaryProvisioningCipher { + PrimaryProvisioningCipher {} } pub fn decrypt(&self, _temp_identity: IdentityKeyPair, bytes: Vec) -> ProvisionMessage { //log::info!("temp_identity: {:?}", temp_identity); log::info!("raw uuid Protocol Buffer: {:?}", bytes); ProvisionMessage { - number : "STUB number".to_string(), - aci : "STUB number".to_string(), - pni : "STUB number".to_string(), - } + number: "STUB number".to_string(), + aci: IdentityKeyPair { + service_id: "STUB".to_string(), + djb_identity_key: IdentityKey { + key: "STUB".to_string(), + }, + djb_private_key: IdentityKey { + key: "STUB".to_string(), + }, + }, + pni: IdentityKeyPair { + service_id: "STUB".to_string(), + djb_identity_key: IdentityKey { + key: "STUB".to_string(), + }, + djb_private_key: IdentityKey { + key: "STUB".to_string(), + }, + }, + master_key: "STUB number".to_string(), + profile_key: Some("STUB number".to_string()), + } } } pub struct ProvisionMessage { pub number: String, - pub aci:String, - pub pni:String, + pub aci: IdentityKeyPair, + pub pni: IdentityKeyPair, + pub master_key: String, + pub profile_key: Option, } impl ProvisionMessage { pub fn decode(temp_identity: IdentityKeyPair, bytes: Vec) -> ProvisionMessage { @@ -38,12 +67,14 @@ impl ProvisionMessage { // optional string uuid = 1; // } pub struct ProvisioningUuid { - pub id : String, + pub id: String, } impl ProvisioningUuid { pub fn decode(bytes: Vec) -> ProvisioningUuid { log::info!("raw uuid Protocol Buffer: {:?}", bytes); - ProvisioningUuid {id : "TODO decode uuid Protocol Buffer".to_string()} + ProvisioningUuid { + id: "TODO decode uuid Protocol Buffer".to_string(), + } } } @@ -51,12 +82,12 @@ impl ProvisioningUuid { // use org.signal.libsignal.protocol.IdentityKey; pub struct IdentityKey { - pub key:String, + pub key: String, } impl IdentityKey { pub fn new(key: String) -> Self { - IdentityKey {key} - } + IdentityKey { key } + } pub fn clone(&self) -> IdentityKey { IdentityKey::new(self.key.clone()) @@ -65,11 +96,11 @@ impl IdentityKey { // use org.signal.libsignal.protocol.IdentityKeyPair; pub struct IdentityKeyPair { + pub service_id: String, pub djb_identity_key: IdentityKey, pub djb_private_key: IdentityKey, } - // use org.signal.libsignal.protocol.ecc.Curve; pub struct Curve {} impl Curve { @@ -94,11 +125,14 @@ impl DjbKeyPair { } } - // https://github.com/AsamK/signal-cli/blob/375bdb79485ec90beb9a154112821a4657740b7a/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java#L45-L51 pub fn generate_identity_key_pair() -> IdentityKeyPair { let djb_key_pair = Curve::generate_key_pair(); let djb_identity_key = IdentityKey::new(djb_key_pair.get_public_key().key); let djb_private_key = djb_key_pair.get_private_key(); - IdentityKeyPair { djb_identity_key, djb_private_key } + IdentityKeyPair { + service_id: "STUB".to_string(), + djb_identity_key, + djb_private_key, + } } From 535ee5de111e077b728cc7a765d5db4f48ee8624 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Wed, 27 Dec 2023 19:03:29 +1100 Subject: [PATCH 38/49] wrap SignalWS in Arc> --- apps/sigchat/src/manager/signal_ws.rs | 62 +++++++++++++++------------ 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/apps/sigchat/src/manager/signal_ws.rs b/apps/sigchat/src/manager/signal_ws.rs index 3e1f06098..f22ecab81 100644 --- a/apps/sigchat/src/manager/signal_ws.rs +++ b/apps/sigchat/src/manager/signal_ws.rs @@ -1,6 +1,7 @@ use rustls::{ClientConnection, StreamOwned}; use std::io::{Error, ErrorKind}; use std::net::TcpStream; +use std::sync::{Arc, Mutex}; use tls::Tls; use tungstenite::{Message, WebSocket}; @@ -9,7 +10,7 @@ const REGISTRATION_PATH: &str = "/v1/registration"; #[allow(dead_code)] pub struct SignalWS { - ws: WebSocket>, + ws: Arc>>>, } impl SignalWS { @@ -18,41 +19,50 @@ impl SignalWS { todo!(); } - pub fn new_provision(host: &str) -> Result { - let server = format!("wss://{host}{}", PROVISIONING_PATH); + fn new(server: &str) -> Result { match SignalWS::connect(server) { - Ok(ws) => Ok(Self { ws }), + Ok(ws) => Ok(Self { + ws: Arc::new(Mutex::new(ws)), + }), Err(e) => Err(e), } } + pub fn new_provision(host: &str) -> Result { + Ok(Self::new(&format!("wss://{host}{}", PROVISIONING_PATH))?) + } + #[allow(dead_code)] pub fn new_register(host: &str) -> Result { - let server = format!("wss://{host}{}", REGISTRATION_PATH); - match SignalWS::connect(server) { - Ok(ws) => Ok(Self { ws }), - Err(e) => Err(e), - } + Ok(Self::new(&format!("wss://{host}{}", REGISTRATION_PATH))?) } pub fn close(&mut self) { - self.ws - .close(None) - .unwrap_or_else(|e| log::warn!("failed to close websocket: {e}")); - // TODO close properly https://docs.rs/tungstenite/0.20.1/tungstenite/protocol/struct.WebSocket.html - self.ws - .flush() - .unwrap_or_else(|e| log::warn!("failed to flush websocket: {e}")); + if let Ok(mut ws) = self.ws.lock() { + ws.close(None) + .unwrap_or_else(|e| log::warn!("failed to close websocket: {e}")); + // TODO close properly https://docs.rs/tungstenite/0.20.1/tungstenite/protocol/struct.WebSocket.html + ws.flush() + .unwrap_or_else(|e| log::warn!("failed to flush websocket: {e}")); + }; } pub fn read(&mut self) -> Result { - match self.ws.read() { - Ok(msg) => Ok(msg), - Err(e) => { - log::warn!("{e}"); - Err(Error::new(ErrorKind::Other, "error on reading ws")) + let msg = if let Ok(mut ws) =self. ws.lock() { + match ws.read() { + Ok(msg) => Ok(msg), + Err(e) => { + log::warn!("{e}"); + Err(Error::new(ErrorKind::Other, "error on reading ws")) + } } - } + } else { + Err(Error::new( + ErrorKind::Other, + "failed to get lock on websocket", + )) + }; + msg } /// Make a websocket connection to host server @@ -62,11 +72,9 @@ impl SignalWS { /// /// # Returns /// - fn connect( - server: String, - ) -> Result>, Error> { + fn connect(server: &str) -> Result>, Error> { log::info!("connecting websocket"); - match url::Url::parse(&server) { + match url::Url::parse(server) { Ok(url) => { log::info!("attempting websocket connection to {}", url.as_str()); let host = url.host_str().expect("failed to extract host from url"); @@ -107,4 +115,4 @@ impl SignalWS { } } } -} \ No newline at end of file +} From 146666cb0e2c1a549f0b856688c8a5d711d91e61 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Wed, 27 Dec 2023 19:41:17 +1100 Subject: [PATCH 39/49] implement sigchat SignalWS read(timeout) --- apps/sigchat/src/manager.rs | 5 +- apps/sigchat/src/manager/signal_ws.rs | 67 +++++++++++++++++++++------ 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index 0d1ce43fd..bc34594ca 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -11,6 +11,7 @@ use locales::t; use modals::Modals; use signal_ws::SignalWS; use std::io::{Error, ErrorKind}; +use std::time::Duration; pub use trust_mode::TrustMode; use tungstenite::Message; @@ -152,7 +153,7 @@ impl Manager { match SignalWS::new_provision(&host) { Ok(mut ws) => { log::info!("provisioning websocket established to {host}"); - match ws.read() { + match ws.read(Some(Duration::from_millis(5000))) { Ok(Message::Binary(uuid)) => { log::info!("received Provisioning UUID message from host"); let uuid = libsignal::ProvisioningUuid::decode(uuid).id.clone(); @@ -173,7 +174,7 @@ impl Manager { Some(device_link_uri.as_str()), ) .expect("qrcode failed"); - match ws.read() { + match ws.read(Some(Duration::from_millis(5000))) { Ok(Message::Binary(registration)) => { log::info!("Registration message received from host"); ws.close(); diff --git a/apps/sigchat/src/manager/signal_ws.rs b/apps/sigchat/src/manager/signal_ws.rs index f22ecab81..b8e7a667e 100644 --- a/apps/sigchat/src/manager/signal_ws.rs +++ b/apps/sigchat/src/manager/signal_ws.rs @@ -2,6 +2,8 @@ use rustls::{ClientConnection, StreamOwned}; use std::io::{Error, ErrorKind}; use std::net::TcpStream; use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; use tls::Tls; use tungstenite::{Message, WebSocket}; @@ -47,22 +49,59 @@ impl SignalWS { }; } - pub fn read(&mut self) -> Result { - let msg = if let Ok(mut ws) =self. ws.lock() { - match ws.read() { - Ok(msg) => Ok(msg), - Err(e) => { - log::warn!("{e}"); - Err(Error::new(ErrorKind::Other, "error on reading ws")) + /// Reads a msg from the websocket with optional timeout + /// + /// Hint: timeout = None is more efficient than Some(Duration(1) + /// + /// # Arguments + /// + /// * `timeout` - a duration before the read operation times-out and returns + /// + /// # Returns + /// a message read from the websocket or ErrorKind::TimedOut + /// + pub fn read(&mut self, timeout: Option) -> Result { + match timeout { + Some(duration) => { + let (tx, rx) = std::sync::mpsc::channel(); + let ws = self.ws.clone(); + thread::spawn(move || { + if let Ok(mut ws) = ws.lock() { + tx.send(ws.read()).unwrap(); + } + }); + match rx.recv_timeout(duration) { + Ok(rx_msg) => match rx_msg { + Ok(msg) => Ok(msg), + Err(e) => { + log::warn!("{e}"); + Err(Error::new(ErrorKind::Other, "error on reading ws")) + } + }, + Err(e) => { + log::warn!("{e}"); + Err(Error::new(ErrorKind::TimedOut, e)) + } } } - } else { - Err(Error::new( - ErrorKind::Other, - "failed to get lock on websocket", - )) - }; - msg + None => { + let msg = if let Ok(mut ws) = self.ws.lock() { + match ws.read() { + Ok(msg) => Ok(msg), + Err(e) => { + log::warn!("{e}"); + Err(Error::new(ErrorKind::Other, "error on reading ws")) + } + } + } else { + Err(Error::new( + ErrorKind::Other, + "failed to get lock on websocket", + )) + }; + msg + } + } } /// Make a websocket connection to host server From 59895ac525157e07afde0f7d924c591d29eaa5b2 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 28 Dec 2023 07:33:21 +1100 Subject: [PATCH 40/49] add sigchat.status.initializing --- apps/sigchat/locales/i18n.json | 4 ++++ apps/sigchat/src/lib.rs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/apps/sigchat/locales/i18n.json b/apps/sigchat/locales/i18n.json index a7a03527e..ea7906a8d 100644 --- a/apps/sigchat/locales/i18n.json +++ b/apps/sigchat/locales/i18n.json @@ -66,6 +66,10 @@ "en": "connecting", "en-tts": "connecting" }, + "sigchat.status.initializing": { + "en": "initializing Signal account in pddb", + "en-tts": "initializing Signal account in pddb" + }, "sigchat.status.offline": { "en": "Signal offline", "en-tts": "Signal offline" diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index 8aa7d81be..7b9210e35 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -205,6 +205,9 @@ impl<'a> SigChat<'a> { /// pub fn account_link(&mut self, host: &str) -> Result { log::info!("Attempting to Link to an existing Signal Account"); + self.chat + .set_status_text(t!("sigchat.status.initializing", locales::LANG)); + self.chat.set_busy_state(true); match Account::new(SIGCHAT_ACCOUNT) { Ok(account) => { let mut manager = Manager::new(account, TrustMode::OnFirstUse); From 3d5030f142421e766541dff3b03425435785431a Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 28 Dec 2023 09:49:23 +1100 Subject: [PATCH 41/49] relocate sigchat service environment argument to lib.rs temporarily default to Staging --- apps/sigchat/src/account.rs | 20 +++++++++----------- apps/sigchat/src/lib.rs | 8 ++++++-- apps/sigchat/src/manager.rs | 13 +++++++++++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/apps/sigchat/src/account.rs b/apps/sigchat/src/account.rs index a32722ce2..0f9a2a245 100644 --- a/apps/sigchat/src/account.rs +++ b/apps/sigchat/src/account.rs @@ -2,7 +2,7 @@ mod service_environment; use crate::manager::libsignal::{DeviceNameUtil, ProvisionMessage}; use pddb::Pddb; -use service_environment::ServiceEnvironment; +pub use service_environment::ServiceEnvironment; use std::io::{Error, ErrorKind, Read, Write}; use std::str::FromStr; @@ -24,7 +24,7 @@ pub struct Account { pni_service_id: Option, profile_key: Option, registered: bool, - service_environment: ServiceEnvironment, + service_environment: Option, storage_key: Option, store_last_receive_timestamp: i64, store_manifest_version: i64, @@ -86,12 +86,7 @@ impl Account { set(&pddb, pddb_dict, PNI_SERVICE_ID_KEY, None)?; set(&pddb, pddb_dict, PROFILE_KEY_KEY, None)?; set(&pddb, pddb_dict, REGISTERED_KEY, Some(&false.to_string()))?; - set( - &pddb, - pddb_dict, - SERVICE_ENVIRONMENT_KEY, - Some(&ServiceEnvironment::Staging.to_string()), - )?; + set(&pddb, pddb_dict, SERVICE_ENVIRONMENT_KEY, None)?; set(&pddb, pddb_dict, STORAGE_KEY_KEY, None)?; set( &pddb, @@ -152,7 +147,7 @@ impl Account { Ok(pni_service_id), Ok(profile_key), Ok(Some(registered)), - Ok(Some(service_environment)), + Ok(service_environment), Ok(storage_key), Ok(Some(store_last_receive_timestamp)), Ok(Some(store_manifest_version)), @@ -174,7 +169,8 @@ impl Account { pni_service_id: pni_service_id, profile_key: profile_key, registered: registered.parse().unwrap(), - service_environment: ServiceEnvironment::from_str(&service_environment).unwrap(), + service_environment: service_environment + .map(|se| ServiceEnvironment::from_str(&se).unwrap()), storage_key: storage_key, store_last_receive_timestamp: store_last_receive_timestamp.parse().unwrap(), store_manifest_version: store_manifest_version.parse().unwrap(), @@ -220,6 +216,7 @@ impl Account { pub fn link( &mut self, device_name: &str, + service_environment: &str, provisioning_msg: ProvisionMessage, ) -> Result { // TODO complete link pre-checks @@ -257,6 +254,7 @@ impl Account { ), )?; self.set(REGISTERED_KEY, Some(&false.to_string()))?; + self.set(SERVICE_ENVIRONMENT_KEY, Some(service_environment))?; self.set(STORAGE_KEY_KEY, None)?; self.set(STORE_LAST_RECEIVE_TIMESTAMP_KEY, Some("0"))?; self.set(STORE_MANIFEST_VERSION_KEY, Some("-1"))?; @@ -330,7 +328,7 @@ impl Account { PROFILE_KEY_KEY => Ok(self.profile_key = owned_value), REGISTERED_KEY => Ok(self.registered = owned_value.unwrap().parse().unwrap()), SERVICE_ENVIRONMENT_KEY => Ok(self.service_environment = - ServiceEnvironment::from_str(&value.unwrap()).unwrap()), + Some(ServiceEnvironment::from_str(&value.unwrap()).unwrap())), STORAGE_KEY_KEY => Ok(self.storage_key = owned_value), _ => { log::warn!("invalid key: {key}"); diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index 7b9210e35..19b98c5ed 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -2,7 +2,7 @@ mod account; pub mod api; mod manager; -use crate::account::Account; +use crate::account::{Account, ServiceEnvironment}; use crate::manager::{Manager, TrustMode}; pub use api::*; use chat::Chat; @@ -218,7 +218,11 @@ impl<'a> SigChat<'a> { self.chat .set_status_text(t!("sigchat.status.connecting", locales::LANG)); self.chat.set_busy_state(true); - match manager.link(Some(&name), Some(&host)) { + match manager.link( + Some(&name), + Some(&host), + Some(&ServiceEnvironment::Staging.to_string()), + ) { Ok(true) => { log::info!("Linked Signal Account"); self.chat.set_busy_state(false); diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index bc34594ca..f34e3ba6b 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -4,7 +4,7 @@ mod link_state; mod signal_ws; mod trust_mode; -use crate::{Account, DEFAULT_DEVICE_NAME, DEFAULT_HOST_NAME}; +use crate::{Account, ServiceEnvironment, DEFAULT_DEVICE_NAME, DEFAULT_HOST_NAME}; use group_permission::GroupPermission; use link_state::LinkState; use locales::t; @@ -143,12 +143,20 @@ impl Manager { /// # Arguments /// * `name` - Optionally specify a name to describe this new device (defaults to "xous"). /// * `host` - Optionally specify a host to connect to (defaults to "signal.org"). + /// * `service_environment` - Optionally specify a service_environment to connect to (defaults to "Live"). /// #[allow(dead_code)] - pub fn link(&mut self, name: Option<&str>, host: Option<&str>) -> Result { + pub fn link( + &mut self, + name: Option<&str>, + host: Option<&str>, + service_environment: Option<&str>, + ) -> Result { // insert default values as required let name = name.unwrap_or(DEFAULT_DEVICE_NAME); let host = host.unwrap_or(DEFAULT_HOST_NAME); + let live_service_environment = ServiceEnvironment::Live.to_string(); + let service_environment = service_environment.unwrap_or(&live_service_environment); let link_err = Error::new(ErrorKind::Other, "failed to link device"); match SignalWS::new_provision(&host) { Ok(mut ws) => { @@ -180,6 +188,7 @@ impl Manager { ws.close(); match self.account.link( name, + service_environment, libsignal::ProvisionMessage::decode( identity_key_pair, registration, From 89cdbc899e1042fd78e8cb58b480c0b3454a52dc Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 28 Dec 2023 15:26:53 +1100 Subject: [PATCH 42/49] ensure that an incomplete pddb dict sigchat.account is deleted --- apps/sigchat/src/lib.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index 19b98c5ed..07272b105 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -226,10 +226,15 @@ impl<'a> SigChat<'a> { Ok(true) => { log::info!("Linked Signal Account"); self.chat.set_busy_state(false); + Ok(Account::read(SIGCHAT_ACCOUNT)?) } Ok(false) => { log::info!("failed to link Signal Account"); self.chat.set_busy_state(false); + Err(Error::new( + ErrorKind::Other, + "failed to link Signal Account", + )) } Err(e) => { log::warn!("error while linking Signal Account: {e}"); @@ -240,6 +245,10 @@ impl<'a> SigChat<'a> { self.modals .show_notification(&format!("{}", e), None) .expect("notification failed"); + Err(Error::new( + ErrorKind::Other, + "error while linking Signal Account", + )) } } } @@ -251,9 +260,12 @@ impl<'a> SigChat<'a> { self.modals .show_notification(t!("sigchat.account.failed", locales::LANG), None) .expect("notification failed"); + Err(Error::new( + ErrorKind::Other, + "failed to create new Account in pddb", + )) } - }; - Ok(Account::read(SIGCHAT_ACCOUNT)?) + } } /// Prompt a name from the user From 7346f03c1a8f6bb689997baff63e335666effdaf Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Thu, 28 Dec 2023 15:27:58 +1100 Subject: [PATCH 43/49] close sigchat websocket properly --- apps/sigchat/src/manager.rs | 7 +++--- apps/sigchat/src/manager/signal_ws.rs | 34 ++++++++++++++++++++------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index f34e3ba6b..8a70721ef 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -161,7 +161,7 @@ impl Manager { match SignalWS::new_provision(&host) { Ok(mut ws) => { log::info!("provisioning websocket established to {host}"); - match ws.read(Some(Duration::from_millis(5000))) { + let result = match ws.read(Some(Duration::from_millis(5000))) { Ok(Message::Binary(uuid)) => { log::info!("received Provisioning UUID message from host"); let uuid = libsignal::ProvisioningUuid::decode(uuid).id.clone(); @@ -185,7 +185,6 @@ impl Manager { match ws.read(Some(Duration::from_millis(5000))) { Ok(Message::Binary(registration)) => { log::info!("Registration message received from host"); - ws.close(); match self.account.link( name, service_environment, @@ -225,7 +224,9 @@ impl Manager { log::warn!("{e}"); Err(link_err) } - } + }; + ws.close(); + result } Err(e) => { log::info!("failed to connect to server: {}", e); diff --git a/apps/sigchat/src/manager/signal_ws.rs b/apps/sigchat/src/manager/signal_ws.rs index b8e7a667e..beb0da0c7 100644 --- a/apps/sigchat/src/manager/signal_ws.rs +++ b/apps/sigchat/src/manager/signal_ws.rs @@ -40,13 +40,30 @@ impl SignalWS { } pub fn close(&mut self) { - if let Ok(mut ws) = self.ws.lock() { - ws.close(None) - .unwrap_or_else(|e| log::warn!("failed to close websocket: {e}")); - // TODO close properly https://docs.rs/tungstenite/0.20.1/tungstenite/protocol/struct.WebSocket.html - ws.flush() - .unwrap_or_else(|e| log::warn!("failed to flush websocket: {e}")); - }; + log::info!("attempting to close websocket connection"); + let ws = self.ws.clone(); + thread::spawn(move || loop { + if let Ok(mut ws) = ws.lock() { + ws.close(None) + .unwrap_or_else(|e| log::warn!("failed to close websocket: {e}")); + loop { + match ws.flush() { + Ok(()) => (), + Err( + tungstenite::Error::ConnectionClosed + | tungstenite::Error::AlreadyClosed, + ) => { + log::info!("websocket connection closed"); + break; + } + Err(e) => { + log::warn!("{e}"); + break; + } + } + } + }; + }); } /// Reads a msg from the websocket with optional timeout @@ -67,7 +84,8 @@ impl SignalWS { let ws = self.ws.clone(); thread::spawn(move || { if let Ok(mut ws) = ws.lock() { - tx.send(ws.read()).unwrap(); + tx.send(ws.read()) + .unwrap_or_else(|e| log::warn!("failed to forward ws msg: {e}")); } }); match rx.recv_timeout(duration) { From fa66cdfc01ae593dbefad8d0fac7a86c77538ad1 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Fri, 29 Dec 2023 07:41:41 +1100 Subject: [PATCH 44/49] implement sigchat host_modal() with validation --- apps/sigchat/locales/i18n.json | 4 +++ apps/sigchat/src/lib.rs | 48 ++++++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/apps/sigchat/locales/i18n.json b/apps/sigchat/locales/i18n.json index ea7906a8d..f90be8546 100644 --- a/apps/sigchat/locales/i18n.json +++ b/apps/sigchat/locales/i18n.json @@ -35,6 +35,10 @@ "en": "Setup Signal on this device.", "en-tts": "Setup Signal on this device." }, + "sigchat.host.invalid": { + "en": "Invalid host format", + "en-tts": "Invalid host format" + }, "sigchat.menu.close": { "en": "Close menu", "en-tts": "Close menu" diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index 07272b105..dce5d00c0 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -120,10 +120,7 @@ impl<'a> SigChat<'a> { match self.modals.get_radio_index() { Ok(index) => match index { 0 => { - let host = self.name_modal( - DEFAULT_HOST_NAME, - t!("sigchat.account.host.name", locales::LANG), - ); + let host = self.host_modal(); match self.probe_host(&host) { true => Ok(self.account_link(&host)?), false => Err(Error::new( @@ -133,10 +130,7 @@ impl<'a> SigChat<'a> { } } 1 => { - let host = self.name_modal( - DEFAULT_HOST_NAME, - t!("sigchat.account.host.name", locales::LANG), - ); + let host = self.host_modal(); match self.probe_host(&host) { true => Ok(self.account_register(&host)?), false => Err(Error::new( @@ -164,6 +158,44 @@ impl<'a> SigChat<'a> { } } + /// Prompt for a host name from the user + /// + /// # Returns + /// + /// the host provided by the user + /// + fn host_modal(&self) -> Host { + let mut host = None; + while host.is_none() { + host = match self + .modals + .alert_builder(t!("sigchat.account.host.name", locales::LANG)) + .field(Some(DEFAULT_HOST_NAME.to_string()), None) + .build() + { + Ok(text) => match Host::parse(&text.content()[0].content.to_string()) { + Ok(host) => match host { + Host::Domain(..) => Some(host), + _ => { + self.modals + .show_notification(t!("sigchat.host.invalid", locales::LANG), None) + .expect("notification failed"); + None + } + }, + Err(_) => { + self.modals + .show_notification(t!("sigchat.host.invalid", locales::LANG), None) + .expect("notification failed"); + None + } + }, + _ => Host::parse(DEFAULT_HOST_NAME).ok(), + } + } + host.unwrap() + } + /// Probe host for tls Certificate Authority chain of trust /// /// # Arguments From 8f2f464e17aaef7ea9b9d093400ef62612fa31f6 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Fri, 29 Dec 2023 07:54:05 +1100 Subject: [PATCH 45/49] store host in sigchat Account --- apps/sigchat/src/account.rs | 83 ++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/apps/sigchat/src/account.rs b/apps/sigchat/src/account.rs index 0f9a2a245..027fdec26 100644 --- a/apps/sigchat/src/account.rs +++ b/apps/sigchat/src/account.rs @@ -5,6 +5,7 @@ use pddb::Pddb; pub use service_environment::ServiceEnvironment; use std::io::{Error, ErrorKind, Read, Write}; use std::str::FromStr; +use url::Host; #[allow(dead_code)] pub struct Account { @@ -15,6 +16,7 @@ pub struct Account { aci_service_id: Option, device_id: u32, encrypted_device_name: Option, + host: Host, is_multi_device: bool, number: Option, password: Option, @@ -31,11 +33,14 @@ pub struct Account { store_manifest: Option, } +pub const DEFAULT_HOST: &str = "signal.org"; + const ACI_IDENTITY_PRIVATE_KEY: &str = "aci.identity.private"; const ACI_IDENTITY_PUBLIC_KEY: &str = "aci.identity.public"; const ACI_SERVICE_ID_KEY: &str = "aci.service_id"; const DEVICE_ID_KEY: &str = "device_id"; const ENCRYPTED_DEVICE_NAME_KEY: &str = "encrypted_device_name"; +const HOST_KEY: &str = "host"; const IS_MULTI_DEVICE_KEY: &str = "is_multi_device"; const NUMBER_KEY: &str = "number"; const PASSWORD_KEY: &str = "password"; @@ -52,18 +57,18 @@ const STORE_MANIFEST_VERSION_KEY: &str = "store_manifest_version"; const STORE_MANIFEST_KEY: &str = "store_manifest"; impl Account { - // Create a new Account stored in pddb with default values - // - // This function saves default values for each field in the pddb - // and then calls read() to load the values into the Account struct - // - // # Arguments - // * `pddb_dict` - the pddb dictionary name to hold the Account - // - // # Returns - // - // a new Account with default values - // + /// Create a new Account stored in pddb with default values + /// + /// This function saves default values for each field in the pddb + /// and then calls read() to load the values into the Account struct + /// + /// # Arguments + /// * `pddb_dict` - pddb dictionary name to hold the Account + /// + /// # Returns + /// + /// a new Account with default values + /// pub fn new(pddb_dict: &str) -> Result { let pddb = pddb::Pddb::new(); pddb.try_mount(); @@ -72,6 +77,7 @@ impl Account { set(&pddb, pddb_dict, ACI_SERVICE_ID_KEY, None)?; set(&pddb, pddb_dict, DEVICE_ID_KEY, Some("0"))?; set(&pddb, pddb_dict, ENCRYPTED_DEVICE_NAME_KEY, None)?; + set(&pddb, pddb_dict, HOST_KEY, Some(DEFAULT_HOST))?; set( &pddb, pddb_dict, @@ -117,6 +123,7 @@ impl Account { get(&pddb, pddb_dict, ACI_SERVICE_ID_KEY), get(&pddb, pddb_dict, DEVICE_ID_KEY), get(&pddb, pddb_dict, ENCRYPTED_DEVICE_NAME_KEY), + get(&pddb, pddb_dict, HOST_KEY), get(&pddb, pddb_dict, IS_MULTI_DEVICE_KEY), get(&pddb, pddb_dict, NUMBER_KEY), get(&pddb, pddb_dict, PASSWORD_KEY), @@ -138,6 +145,7 @@ impl Account { Ok(aci_service_id), Ok(Some(device_id)), Ok(encrypted_device_name), + Ok(Some(host)), Ok(Some(is_multi_device)), Ok(number), Ok(password), @@ -160,6 +168,7 @@ impl Account { aci_service_id: aci_service_id, device_id: device_id.parse().unwrap(), encrypted_device_name: encrypted_device_name, + host: Host::parse(&host).unwrap(), is_multi_device: is_multi_device.parse().unwrap(), number: number, password: password, @@ -176,26 +185,27 @@ impl Account { store_manifest_version: store_manifest_version.parse().unwrap(), store_manifest: store_manifest, }), - (Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), - (_, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), - (_, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), - (_, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), - (_, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), - (_, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), - (_, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), - (_, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _) => Err(e), - (_, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _) => Err(e), - (_, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _) => Err(e), - (_, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _) => Err(e), - (_, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _) => Err(e), - (_, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _) => Err(e), - (_, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _) => Err(e), - (_, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _) => Err(e), - (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _) => Err(e), - (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _) => Err(e), - (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _) => Err(e), - (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e)) => Err(e), - (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => { + (Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _, _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e), _) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, Err(e)) => Err(e), + (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => { Err(Error::from(ErrorKind::InvalidData)) } } @@ -216,7 +226,8 @@ impl Account { pub fn link( &mut self, device_name: &str, - service_environment: &str, + host: &Host, + service_environment: &ServiceEnvironment, provisioning_msg: ProvisionMessage, ) -> Result { // TODO complete link pre-checks @@ -237,6 +248,7 @@ impl Account { aci.djb_private_key, )), )?; + self.set(HOST_KEY, Some(&host.to_string()))?; self.set(IS_MULTI_DEVICE_KEY, Some(&true.to_string()))?; self.set(NUMBER_KEY, Some(&provisioning_msg.number))?; self.set(PASSWORD_KEY, Some("STUB 32 bytes"))?; @@ -254,7 +266,10 @@ impl Account { ), )?; self.set(REGISTERED_KEY, Some(&false.to_string()))?; - self.set(SERVICE_ENVIRONMENT_KEY, Some(service_environment))?; + self.set( + SERVICE_ENVIRONMENT_KEY, + Some(&service_environment.to_string()), + )?; self.set(STORAGE_KEY_KEY, None)?; self.set(STORE_LAST_RECEIVE_TIMESTAMP_KEY, Some("0"))?; self.set(STORE_MANIFEST_VERSION_KEY, Some("-1"))?; From 7eb1c072694a8a62f4feea233e9a183699c25bf5 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Fri, 29 Dec 2023 10:13:31 +1100 Subject: [PATCH 46/49] implement sigchat Manager Config Tie the Staging Environment specifics to the Account Manager Note sigchat is hard coded for the Staging Environment for now --- apps/sigchat/src/account.rs | 39 +++++---- .../src/account/service_environment.rs | 3 +- apps/sigchat/src/api.rs | 1 - apps/sigchat/src/lib.rs | 44 +++++----- apps/sigchat/src/manager.rs | 32 ++++---- apps/sigchat/src/manager/config.rs | 38 +++++++++ apps/sigchat/src/manager/signal_ws.rs | 81 ++++++++++--------- 7 files changed, 145 insertions(+), 93 deletions(-) create mode 100644 apps/sigchat/src/manager/config.rs diff --git a/apps/sigchat/src/account.rs b/apps/sigchat/src/account.rs index 027fdec26..6449f3845 100644 --- a/apps/sigchat/src/account.rs +++ b/apps/sigchat/src/account.rs @@ -26,7 +26,7 @@ pub struct Account { pni_service_id: Option, profile_key: Option, registered: bool, - service_environment: Option, + service_environment: ServiceEnvironment, storage_key: Option, store_last_receive_timestamp: i64, store_manifest_version: i64, @@ -69,7 +69,11 @@ impl Account { /// /// a new Account with default values /// - pub fn new(pddb_dict: &str) -> Result { + pub fn new( + pddb_dict: &str, + host: &Host, + service_environment: &ServiceEnvironment, + ) -> Result { let pddb = pddb::Pddb::new(); pddb.try_mount(); set(&pddb, pddb_dict, ACI_IDENTITY_PRIVATE_KEY, None)?; @@ -77,7 +81,7 @@ impl Account { set(&pddb, pddb_dict, ACI_SERVICE_ID_KEY, None)?; set(&pddb, pddb_dict, DEVICE_ID_KEY, Some("0"))?; set(&pddb, pddb_dict, ENCRYPTED_DEVICE_NAME_KEY, None)?; - set(&pddb, pddb_dict, HOST_KEY, Some(DEFAULT_HOST))?; + set(&pddb, pddb_dict, HOST_KEY, Some(&host.to_string()))?; set( &pddb, pddb_dict, @@ -92,7 +96,12 @@ impl Account { set(&pddb, pddb_dict, PNI_SERVICE_ID_KEY, None)?; set(&pddb, pddb_dict, PROFILE_KEY_KEY, None)?; set(&pddb, pddb_dict, REGISTERED_KEY, Some(&false.to_string()))?; - set(&pddb, pddb_dict, SERVICE_ENVIRONMENT_KEY, None)?; + set( + &pddb, + pddb_dict, + SERVICE_ENVIRONMENT_KEY, + Some(&service_environment.to_string()), + )?; set(&pddb, pddb_dict, STORAGE_KEY_KEY, None)?; set( &pddb, @@ -155,7 +164,7 @@ impl Account { Ok(pni_service_id), Ok(profile_key), Ok(Some(registered)), - Ok(service_environment), + Ok(Some(service_environment)), Ok(storage_key), Ok(Some(store_last_receive_timestamp)), Ok(Some(store_manifest_version)), @@ -178,8 +187,7 @@ impl Account { pni_service_id: pni_service_id, profile_key: profile_key, registered: registered.parse().unwrap(), - service_environment: service_environment - .map(|se| ServiceEnvironment::from_str(&se).unwrap()), + service_environment: ServiceEnvironment::from_str(&service_environment).unwrap(), storage_key: storage_key, store_last_receive_timestamp: store_last_receive_timestamp.parse().unwrap(), store_manifest_version: store_manifest_version.parse().unwrap(), @@ -226,8 +234,6 @@ impl Account { pub fn link( &mut self, device_name: &str, - host: &Host, - service_environment: &ServiceEnvironment, provisioning_msg: ProvisionMessage, ) -> Result { // TODO complete link pre-checks @@ -248,7 +254,6 @@ impl Account { aci.djb_private_key, )), )?; - self.set(HOST_KEY, Some(&host.to_string()))?; self.set(IS_MULTI_DEVICE_KEY, Some(&true.to_string()))?; self.set(NUMBER_KEY, Some(&provisioning_msg.number))?; self.set(PASSWORD_KEY, Some("STUB 32 bytes"))?; @@ -266,10 +271,6 @@ impl Account { ), )?; self.set(REGISTERED_KEY, Some(&false.to_string()))?; - self.set( - SERVICE_ENVIRONMENT_KEY, - Some(&service_environment.to_string()), - )?; self.set(STORAGE_KEY_KEY, None)?; self.set(STORE_LAST_RECEIVE_TIMESTAMP_KEY, Some("0"))?; self.set(STORE_MANIFEST_VERSION_KEY, Some("-1"))?; @@ -288,6 +289,10 @@ impl Account { Ok(false) } + pub fn host(&self) -> &Host { + &self.host + } + #[allow(dead_code)] pub fn number(&self) -> Option<&str> { match &self.number { @@ -296,6 +301,10 @@ impl Account { } } + pub fn service_environment(&self) -> &ServiceEnvironment { + &self.service_environment + } + #[allow(dead_code)] pub fn set_number(&mut self, value: &str) -> Result<(), Error> { match self.set(NUMBER_KEY, Some(value)) { @@ -343,7 +352,7 @@ impl Account { PROFILE_KEY_KEY => Ok(self.profile_key = owned_value), REGISTERED_KEY => Ok(self.registered = owned_value.unwrap().parse().unwrap()), SERVICE_ENVIRONMENT_KEY => Ok(self.service_environment = - Some(ServiceEnvironment::from_str(&value.unwrap()).unwrap())), + ServiceEnvironment::from_str(&value.unwrap()).unwrap()), STORAGE_KEY_KEY => Ok(self.storage_key = owned_value), _ => { log::warn!("invalid key: {key}"); diff --git a/apps/sigchat/src/account/service_environment.rs b/apps/sigchat/src/account/service_environment.rs index 64216d583..a0f17b8d3 100644 --- a/apps/sigchat/src/account/service_environment.rs +++ b/apps/sigchat/src/account/service_environment.rs @@ -1,9 +1,8 @@ -use rkyv::{Archive, Deserialize, Serialize}; use std::fmt; use std::str::FromStr; // The server environment to use: -#[derive(Archive, Serialize, Deserialize, Debug)] +#[derive(Clone, Debug)] pub enum ServiceEnvironment { Live, Staging, diff --git a/apps/sigchat/src/api.rs b/apps/sigchat/src/api.rs index e7f850f0d..c46640c3e 100644 --- a/apps/sigchat/src/api.rs +++ b/apps/sigchat/src/api.rs @@ -1,7 +1,6 @@ #[allow(dead_code)] pub(crate) const SERVER_NAME_SIGCHAT: &str = "_Signal chat_"; pub(crate) const DEFAULT_DEVICE_NAME: &str = "xous"; -pub(crate) const DEFAULT_HOST_NAME: &str = "chat.signal.org"; #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] pub enum SigchatOp { diff --git a/apps/sigchat/src/lib.rs b/apps/sigchat/src/lib.rs index dce5d00c0..0e516fcdc 100644 --- a/apps/sigchat/src/lib.rs +++ b/apps/sigchat/src/lib.rs @@ -2,14 +2,15 @@ mod account; pub mod api; mod manager; -use crate::account::{Account, ServiceEnvironment}; -use crate::manager::{Manager, TrustMode}; +use crate::account::{Account, ServiceEnvironment, DEFAULT_HOST}; +use crate::manager::{Config, Manager, TrustMode}; pub use api::*; use chat::Chat; use locales::t; use modals::Modals; use std::io::{Error, ErrorKind}; use tls::Tls; +use url::{Host, Url}; /// PDDB Dict for sigchat keys const SIGCHAT_ACCOUNT: &str = "sigchat.account"; @@ -105,6 +106,7 @@ impl<'a> SigChat<'a> { /// fn account_setup(&mut self) -> Result { log::info!("Attempting to setup a Signal Account"); + let service_environment = ServiceEnvironment::Staging; self.modals .add_list_item(t!("sigchat.account.link", locales::LANG)) .expect("failed add list item"); @@ -121,8 +123,9 @@ impl<'a> SigChat<'a> { Ok(index) => match index { 0 => { let host = self.host_modal(); - match self.probe_host(&host) { - true => Ok(self.account_link(&host)?), + let config = Config::new(host, service_environment); + match self.probe_host(config.url()) { + true => Ok(self.account_link(&config)?), false => Err(Error::new( ErrorKind::Other, "failed to trust host certificate", @@ -131,8 +134,9 @@ impl<'a> SigChat<'a> { } 1 => { let host = self.host_modal(); - match self.probe_host(&host) { - true => Ok(self.account_register(&host)?), + let config = Config::new(host, service_environment); + match self.probe_host(config.url()) { + true => Ok(self.account_register(&config)?), false => Err(Error::new( ErrorKind::Other, "failed to trust host certificate", @@ -170,7 +174,7 @@ impl<'a> SigChat<'a> { host = match self .modals .alert_builder(t!("sigchat.account.host.name", locales::LANG)) - .field(Some(DEFAULT_HOST_NAME.to_string()), None) + .field(Some(DEFAULT_HOST.to_string()), None) .build() { Ok(text) => match Host::parse(&text.content()[0].content.to_string()) { @@ -190,7 +194,7 @@ impl<'a> SigChat<'a> { None } }, - _ => Host::parse(DEFAULT_HOST_NAME).ok(), + _ => Host::parse(DEFAULT_HOST).ok(), } } host.unwrap() @@ -204,12 +208,12 @@ impl<'a> SigChat<'a> { /// # Returns /// true if at least 1 Certificate Authority is trusted /// - fn probe_host(&self, host: &str) -> bool { + fn probe_host(&self, url: &Url) -> bool { self.chat .set_status_text(t!("sigchat.status.probing", locales::LANG)); self.chat.set_busy_state(true); let tls = Tls::new(); - if tls.accessible(host, true) { + if tls.accessible(url.host_str().unwrap(), true) { self.chat.set_busy_state(false); true } else { @@ -230,17 +234,17 @@ impl<'a> SigChat<'a> { /// commences - culminating by presenting a qr-code to be scanned by the primary device. /// /// # Arguments - /// * `host` - the Signal server host name or ip-address + /// * `config` - Signal configuration - host server and Live/Staging environment /// /// # Returns /// Account struct stored in pddb /// - pub fn account_link(&mut self, host: &str) -> Result { + pub fn account_link(&mut self, config: &Config) -> Result { log::info!("Attempting to Link to an existing Signal Account"); self.chat .set_status_text(t!("sigchat.status.initializing", locales::LANG)); self.chat.set_busy_state(true); - match Account::new(SIGCHAT_ACCOUNT) { + match Account::new(SIGCHAT_ACCOUNT, config.host(), config.service_environment()) { Ok(account) => { let mut manager = Manager::new(account, TrustMode::OnFirstUse); let name = self.name_modal( @@ -250,11 +254,7 @@ impl<'a> SigChat<'a> { self.chat .set_status_text(t!("sigchat.status.connecting", locales::LANG)); self.chat.set_busy_state(true); - match manager.link( - Some(&name), - Some(&host), - Some(&ServiceEnvironment::Staging.to_string()), - ) { + match manager.link(&name) { Ok(true) => { log::info!("Linked Signal Account"); self.chat.set_busy_state(false); @@ -327,7 +327,11 @@ impl<'a> SigChat<'a> { /// The phone number must include the country calling code, i.e. the number must start with a "+" sign. /// Warning: this will disable the authentication of your phone as a primary device. /// - pub fn account_register(&mut self, _host: &str) -> Result { + /// # Arguments + /// + /// * `config` - Signal configuration - host server and Live/Staging environment + /// + pub fn account_register(&mut self, config: &Config) -> Result { log::info!("Attempting to Register a new Signal Account"); self.modals .show_notification(&"sorry - registration is not implemented yet", None) @@ -335,7 +339,7 @@ impl<'a> SigChat<'a> { match self.number_modal() { Ok(number) => { log::info!("registration phone number = {:?}", number); - match Account::new(SIGCHAT_ACCOUNT) { + match Account::new(SIGCHAT_ACCOUNT, config.host(), config.service_environment()) { Ok(mut account) => match account.set_number(&number.to_string()) { Ok(_number) => { self.manager = Some(Manager::new(account, TrustMode::OnFirstUse)); diff --git a/apps/sigchat/src/manager.rs b/apps/sigchat/src/manager.rs index 8a70721ef..01860e721 100644 --- a/apps/sigchat/src/manager.rs +++ b/apps/sigchat/src/manager.rs @@ -1,10 +1,12 @@ +mod config; mod group_permission; pub mod libsignal; // stub mod link_state; mod signal_ws; mod trust_mode; -use crate::{Account, ServiceEnvironment, DEFAULT_DEVICE_NAME, DEFAULT_HOST_NAME}; +use crate::Account; +pub use config::Config; use group_permission::GroupPermission; use link_state::LinkState; use locales::t; @@ -22,6 +24,7 @@ use tungstenite::Message; #[allow(dead_code)] pub struct Manager { account: Account, + config: Config, trust_mode: TrustMode, websocket: Option, log_verbose: bool, @@ -37,8 +40,13 @@ impl Manager { /// * `account` - Specify your phone number, that will be your identifier. The phone number must include the country calling code, i.e. the number must start with a "+" sign. /// pub fn new(account: Account, trust_mode: TrustMode) -> Manager { + let config = Config::new( + account.host().clone(), + account.service_environment().clone(), + ); Manager { account, + config, trust_mode, websocket: None, log_verbose: false, @@ -141,24 +149,15 @@ impl Manager { /// 7. checking and saving account details /// /// # Arguments - /// * `name` - Optionally specify a name to describe this new device (defaults to "xous"). - /// * `host` - Optionally specify a host to connect to (defaults to "signal.org"). - /// * `service_environment` - Optionally specify a service_environment to connect to (defaults to "Live"). + /// * `name` - name to describe this new device + /// * `config` - Signal configuration - host server and Live/Staging environment + /// * `service_environment` - service_environment to connect to on host /// #[allow(dead_code)] - pub fn link( - &mut self, - name: Option<&str>, - host: Option<&str>, - service_environment: Option<&str>, - ) -> Result { - // insert default values as required - let name = name.unwrap_or(DEFAULT_DEVICE_NAME); - let host = host.unwrap_or(DEFAULT_HOST_NAME); - let live_service_environment = ServiceEnvironment::Live.to_string(); - let service_environment = service_environment.unwrap_or(&live_service_environment); + pub fn link(&mut self, name: &str) -> Result { let link_err = Error::new(ErrorKind::Other, "failed to link device"); - match SignalWS::new_provision(&host) { + let mut host = self.config.url().clone(); + match SignalWS::new_provision(&mut host) { Ok(mut ws) => { log::info!("provisioning websocket established to {host}"); let result = match ws.read(Some(Duration::from_millis(5000))) { @@ -187,7 +186,6 @@ impl Manager { log::info!("Registration message received from host"); match self.account.link( name, - service_environment, libsignal::ProvisionMessage::decode( identity_key_pair, registration, diff --git a/apps/sigchat/src/manager/config.rs b/apps/sigchat/src/manager/config.rs new file mode 100644 index 000000000..572a36cc7 --- /dev/null +++ b/apps/sigchat/src/manager/config.rs @@ -0,0 +1,38 @@ +use crate::ServiceEnvironment; +use url::{Host, Url}; + +pub struct Config { + host: Host, + service_environment: ServiceEnvironment, + url: Url, +} + +impl Config { + pub fn new(host: Host, service_environment: ServiceEnvironment) -> Self { + let host_base = host.to_string(); + match service_environment { + ServiceEnvironment::Live => Self { + host: host, + service_environment: service_environment, + url: Url::parse(&format!("https://chat.{}", host_base)).unwrap(), + }, + ServiceEnvironment::Staging => Self { + host: host, + service_environment: service_environment, + url: Url::parse(&format!("https://chat.staging.{}", host_base)).unwrap(), + }, + } + } + + pub fn host(&self) -> &Host { + &self.host + } + + pub fn service_environment(&self) -> &ServiceEnvironment { + &self.service_environment + } + + pub fn url(&self) -> &Url { + &self.url + } +} diff --git a/apps/sigchat/src/manager/signal_ws.rs b/apps/sigchat/src/manager/signal_ws.rs index beb0da0c7..70a2f17d8 100644 --- a/apps/sigchat/src/manager/signal_ws.rs +++ b/apps/sigchat/src/manager/signal_ws.rs @@ -6,9 +6,10 @@ use std::thread; use std::time::Duration; use tls::Tls; use tungstenite::{Message, WebSocket}; +use url::Url; -const PROVISIONING_PATH: &str = "/v1/websocket/provisioning/"; -const REGISTRATION_PATH: &str = "/v1/registration"; +const PROVISIONING_PATH: [&str; 4] = ["v1", "websocket", "provisioning", ""]; +const REGISTRATION_PATH: [&str; 3] = ["v1", "registration", ""]; #[allow(dead_code)] pub struct SignalWS { @@ -21,8 +22,8 @@ impl SignalWS { todo!(); } - fn new(server: &str) -> Result { - match SignalWS::connect(server) { + fn new(url: &Url) -> Result { + match SignalWS::connect(url) { Ok(ws) => Ok(Self { ws: Arc::new(Mutex::new(ws)), }), @@ -30,13 +31,21 @@ impl SignalWS { } } - pub fn new_provision(host: &str) -> Result { - Ok(Self::new(&format!("wss://{host}{}", PROVISIONING_PATH))?) + pub fn new_provision(url: &mut Url) -> Result { + url.set_scheme("wss").expect("failed to set scheme"); + url.path_segments_mut() + .expect("failed to add path") + .extend(&PROVISIONING_PATH); + Ok(Self::new(&url)?) } #[allow(dead_code)] - pub fn new_register(host: &str) -> Result { - Ok(Self::new(&format!("wss://{host}{}", REGISTRATION_PATH))?) + pub fn new_register(url: &mut Url) -> Result { + url.set_scheme("wss").expect("failed to set scheme"); + url.path_segments_mut() + .expect("failed to add path") + .extend(®ISTRATION_PATH); + Ok(Self::new(&url)?) } pub fn close(&mut self) { @@ -122,53 +131,49 @@ impl SignalWS { } } + #[allow(dead_code)] + pub fn send(&mut self, _message: Message) -> Result<(), Error> { + todo!() + } + /// Make a websocket connection to host server /// /// # Arguments - /// * `server` - server + /// * `url` - url of Signal server /// /// # Returns /// - fn connect(server: &str) -> Result>, Error> { - log::info!("connecting websocket"); - match url::Url::parse(server) { - Ok(url) => { - log::info!("attempting websocket connection to {}", url.as_str()); - let host = url.host_str().expect("failed to extract host from url"); - match TcpStream::connect((host, 443)) { - Ok(sock) => { - log::info!("tcp connected to {host}"); - let xtls = Tls::new(); - match xtls.stream_owned(host, sock) { - Ok(tls_stream) => { - log::info!("tls configured"); - match tungstenite::client(url, tls_stream) { - Ok((socket, response)) => { - log::info!("Websocket connected to: {}", server); - log::info!("Response HTTP code: {}", response.status()); - Ok(socket) - } - Err(e) => { - log::info!("failed to connect websocket: {}", e); - Err(Error::from(ErrorKind::ConnectionRefused)) - } - } + fn connect(url: &Url) -> Result>, Error> { + log::info!("attempting websocket connection to {}", url.as_str()); + let host = url.host_str().expect("failed to extract host from url"); + match TcpStream::connect((host, 443)) { + Ok(sock) => { + log::info!("tcp connected to {host}"); + let xtls = Tls::new(); + match xtls.stream_owned(host, sock) { + Ok(tls_stream) => { + log::info!("tls configured"); + match tungstenite::client(url, tls_stream) { + Ok((socket, response)) => { + log::info!("Websocket connected to: {}", url.as_str()); + log::info!("Response HTTP code: {}", response.status()); + Ok(socket) } Err(e) => { - log::warn!("failed to configure tls: {e}"); - Err(e) + log::info!("failed to connect websocket: {}", e); + Err(Error::from(ErrorKind::ConnectionRefused)) } } } Err(e) => { - log::warn!("failed to connect tcp: {e}"); + log::warn!("failed to configure tls: {e}"); Err(e) } } } Err(e) => { - log::info!("failed to parse server url: {e}"); - Err(Error::new(ErrorKind::InvalidData, e)) + log::warn!("failed to connect tcp: {e}"); + Err(e) } } } From 147e60a8053eb2b8870b81f0782ff32877e60eaa Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Fri, 29 Dec 2023 16:23:36 +1100 Subject: [PATCH 47/49] add sigchat Account.link() relink validity checks --- apps/sigchat/src/account.rs | 41 +++++++++++++++++++++++---- apps/sigchat/src/manager/libsignal.rs | 11 ++++++- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/apps/sigchat/src/account.rs b/apps/sigchat/src/account.rs index 6449f3845..9988767d6 100644 --- a/apps/sigchat/src/account.rs +++ b/apps/sigchat/src/account.rs @@ -1,6 +1,6 @@ mod service_environment; -use crate::manager::libsignal::{DeviceNameUtil, ProvisionMessage}; +use crate::manager::libsignal::{DeviceNameUtil, ProvisionMessage, SignalServiceAddress}; use pddb::Pddb; pub use service_environment::ServiceEnvironment; use std::io::{Error, ErrorKind, Read, Write}; @@ -231,16 +231,36 @@ impl Account { Ok(()) } + /// link to an existing Signal Account as a secondary device + /// + /// Confirm that the state of the Signal Account is OK before linking + /// https://github.com/AsamK/signal-cli/blob/375bdb79485ec90beb9a154112821a4657740b7a/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java#L200-L239 + /// + /// # Arguments + /// + /// * `device_name` - name to describe this new device + /// * `provisioning_msg` - obtained from the Signal server + /// + /// # Returns + /// + /// true on success + /// pub fn link( &mut self, device_name: &str, provisioning_msg: ProvisionMessage, ) -> Result { - // TODO complete link pre-checks // Check if this device can be relinked - // check not primary - // check not registered in another environment - // check account state with server + if self.is_primary_device() { + log::warn!("failed to link device as already registered as primary"); + return Ok(false); + } + // TODO complete link pre-checks + // } else if self.is_registered() + // && self.service_environment() != provisioning_msg.service_environment{ + // log::warn!("failed to link device as already registered in different Service Environment"); + // return Ok(false); + // } let aci = provisioning_msg.aci; self.set(ACI_IDENTITY_PRIVATE_KEY, Some(&aci.djb_private_key.key))?; @@ -277,6 +297,7 @@ impl Account { self.set(STORE_MANIFEST_KEY, None)?; // TODO complete registration setup + // https://github.com/AsamK/signal-cli/blob/375bdb79485ec90beb9a154112821a4657740b7a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java#L270-L306 // getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress()); // getSenderKeyStore().deleteAll(); // trustSelfIdentity(ServiceIdType.ACI); @@ -286,13 +307,21 @@ impl Account { // clearAllPreKeys(); // getKeyValueStore().storeEntry(lastRecipientsRefresh, null); - Ok(false) + Ok(true) } pub fn host(&self) -> &Host { &self.host } + pub fn is_primary_device(&self) -> bool { + self.device_id == SignalServiceAddress::DEFAULT_DEVICE_ID + } + + pub fn is_registered(&self) -> bool { + self.registered + } + #[allow(dead_code)] pub fn number(&self) -> Option<&str> { match &self.number { diff --git a/apps/sigchat/src/manager/libsignal.rs b/apps/sigchat/src/manager/libsignal.rs index 5b29e753d..1df181e8f 100644 --- a/apps/sigchat/src/manager/libsignal.rs +++ b/apps/sigchat/src/manager/libsignal.rs @@ -1,6 +1,15 @@ // this is a stub for a wrapped libsignal -// org.whispersystems.signalservice.api.util.DeviceNameUtil; + +// use org.whispersystems.signalservice.api.push.SignalServiceAddress; +pub struct SignalServiceAddress {} +impl SignalServiceAddress { + pub const DEFAULT_DEVICE_ID: u32 = 0; +} + +//////////////////////////////////////////////////////// + +// use org.whispersystems.signalservice.api.util.DeviceNameUtil; pub struct DeviceNameUtil {} impl DeviceNameUtil { pub fn encrypt_device_name(_device_name: &str, _aci_private_key: IdentityKey) -> String { From 130f4f13f51630771112b8f09bf37e2d9e63a049 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Fri, 29 Dec 2023 16:50:47 +1100 Subject: [PATCH 48/49] sigchat Account doco --- apps/sigchat/src/account.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/sigchat/src/account.rs b/apps/sigchat/src/account.rs index 9988767d6..8df2d9dcb 100644 --- a/apps/sigchat/src/account.rs +++ b/apps/sigchat/src/account.rs @@ -7,6 +7,18 @@ use std::io::{Error, ErrorKind, Read, Write}; use std::str::FromStr; use url::Host; +/// The Account struct is architected as a cache over a pddb dictionary. +/// +/// * Creating a new Account inherently involves writing to pddb +/// * Each field has a 1:1 relationship with a pddb.key. +/// * Field values are able to be set individually +/// +/// Steps to ensure consistency: +/// * the default values in a new Account are first written to pddb, and then read back into the struct. +/// * all fields must be successfully read from pddb or read fails with Error +/// * setting a value requires a successful writes to pddb before updating the field +/// + #[allow(dead_code)] pub struct Account { pddb: Pddb, @@ -64,6 +76,8 @@ impl Account { /// /// # Arguments /// * `pddb_dict` - pddb dictionary name to hold the Account + /// * `host` - Signal host server (immutable) + /// * `service_environment` - Signal service-environment (immutable) /// /// # Returns /// From 364b6a5540dc79322968af55dcd6040541835a4b Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Mon, 1 Jan 2024 05:59:01 +1100 Subject: [PATCH 49/49] refine sigchat scan link qrcode instructions to better reflect Signal App --- apps/sigchat/locales/i18n.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sigchat/locales/i18n.json b/apps/sigchat/locales/i18n.json index f90be8546..a9f4d6f9d 100644 --- a/apps/sigchat/locales/i18n.json +++ b/apps/sigchat/locales/i18n.json @@ -20,8 +20,8 @@ "en-tts": "Please provide a name for this device" }, "sigchat.account.link.scan": { - "en": "Please scan with primary Signal device, and then", - "en-tts": "Please scan with primary Signal device, and then" + "en": "1. Open Signal on your phone\n2. Tap into settings, then tap `Linked Devices`\n3. Tap ⊕ or `Link New Device` and scan", + "en-tts": "1. Open Signal on your phone\n2. Tap into settings, then tap `Linked Devices`\n3. Tap ⊕ or `Link New Device` and scan" }, "sigchat.account.offline": { "en": "Read Signal offline",