rust: keychain calls off the tokio runtime; plan reflects as-built crate choices
This commit is contained in:
@@ -25,13 +25,13 @@ style of Leviathan Desktop (`../leviathan/rust`).
|
|||||||
| Terminal emulation | `alacritty_terminal` | VT parser, grid, scrollback, selection and regex search (the same engine Zed uses). We draw it with egui's painter (§6) |
|
| Terminal emulation | `alacritty_terminal` | VT parser, grid, scrollback, selection and regex search (the same engine Zed uses). We draw it with egui's painter (§6) |
|
||||||
| Local shell | `portable-pty` | `forkpty` on Unix, ConPTY on Windows |
|
| Local shell | `portable-pty` | `forkpty` on Unix, ConPTY on Windows |
|
||||||
| Serial | `serialport` | Baud/bits/parity/stop, RTS/CTS and XON/XOFF, DTR/RTS, break, port enumeration |
|
| Serial | `serialport` | Baud/bits/parity/stop, RTS/CTS and XON/XOFF, DTR/RTS, break, port enumeration |
|
||||||
| IRC | `irc` (tokio, rustls) | Registration, PING, CTCP, NickServ via PRIVMSG; reconnect and channel-state persistence are ours |
|
| IRC | none (our own client over std TCP, `rustls` for TLS) | Registration, PING, CTCP, NickServ via PRIVMSG; reconnect and channel-state persistence are ours |
|
||||||
| VNC | `vnc-rs` | RFB handshake, VNC auth, Raw/CopyRect/Tight/ZRLE → egui texture |
|
| VNC | none (our own RFB client; `des`, `flate2`, `image` for Tight JPEG) | RFB 3.3–3.8, VNC auth, Raw/CopyRect/Hextile/ZRLE/Tight → egui texture |
|
||||||
| HTTP (GitHub, updater) | `ureq` 3 | Blocking calls on `spawn_blocking` |
|
| HTTP (GitHub, updater) | `ureq` 3 | Blocking calls on `spawn_blocking` |
|
||||||
| SQLite | `rusqlite` (`bundled`) | SQLite compiled in, so no system library is needed |
|
| SQLite | `rusqlite` (`bundled`) | SQLite compiled in, so no system library is needed |
|
||||||
| Vault, export | `argon2`, `aes-gcm`, `rand` | Native Argon2id at 128 MiB takes about 0.3 s |
|
| Vault, export | `argon2`, `aes-gcm`, `rand` | Native Argon2id at 128 MiB takes about 0.3 s |
|
||||||
| Keychain | `keyring` 3 | Must read entries written by go-keyring (§3) |
|
| Keychain | `keyring` 3 (pure-Rust D-Bus via zbus on Linux) | Must read entries written by go-keyring (§3) |
|
||||||
| TOTP | `totp-rs` | |
|
| TOTP | none (`hmac` + `sha1`, ~30 lines) | RFC 6238, normalised like pquerna/otp |
|
||||||
| JSON | `serde`, `serde_json` | Protocol configs stay JSON columns |
|
| JSON | `serde`, `serde_json` | Protocol configs stay JSON columns |
|
||||||
| Telnet, RAW, recordings | none (tokio `TcpStream`, serde) | Small ports of the Go code |
|
| Telnet, RAW, recordings | none (tokio `TcpStream`, serde) | Small ports of the Go code |
|
||||||
|
|
||||||
@@ -235,6 +235,8 @@ Terminal views are repainted only when their session receives data, and for inpu
|
|||||||
|
|
||||||
## 6a. VNC
|
## 6a. VNC
|
||||||
|
|
||||||
|
**As built:** `vnc-rs` was not used; the fallback below (our own RFB client) is what shipped.
|
||||||
|
|
||||||
- `vnc-rs` on a tokio task, with an optional SSH tunnel through §2's `direct-tcpip`.
|
- `vnc-rs` on a tokio task, with an optional SSH tunnel through §2's `direct-tcpip`.
|
||||||
- Decoded rectangles are applied to an RGBA buffer, and dirty frames update a `TextureHandle`, scaled to fit.
|
- Decoded rectangles are applied to an RGBA buffer, and dirty frames update a `TextureHandle`, scaled to fit.
|
||||||
- Input: pointer → `PointerEvent`; keys → X11 keysyms (port the keysym map from `vnc.js`).
|
- Input: pointer → `PointerEvent`; keys → X11 keysyms (port the keysym map from `vnc.js`).
|
||||||
|
|||||||
@@ -177,6 +177,12 @@ pub const KEYCHAIN_SERVICE: &str = "NexusTerminal";
|
|||||||
/// The service name Alchementary Link wrote under; read as a fallback (§7 of the port plan).
|
/// The service name Alchementary Link wrote under; read as a fallback (§7 of the port plan).
|
||||||
pub const LEGACY_KEYCHAIN_SERVICE: &str = "AlchementaryLink";
|
pub const LEGACY_KEYCHAIN_SERVICE: &str = "AlchementaryLink";
|
||||||
|
|
||||||
|
/// Runs a keychain call on a plain thread: the D-Bus backend may start its own async runtime,
|
||||||
|
/// which panics if the caller is already inside tokio (as SSH authentication is).
|
||||||
|
fn off_runtime<T: Send + 'static>(f: impl FnOnce() -> T + Send + 'static) -> T {
|
||||||
|
std::thread::spawn(f).join().expect("keychain thread panicked")
|
||||||
|
}
|
||||||
|
|
||||||
fn entry(service: &str, key: &str) -> keyring::Result<keyring::Entry> {
|
fn entry(service: &str, key: &str) -> keyring::Result<keyring::Entry> {
|
||||||
// go-keyring's Windows target is "service:user"; keyring's default differs.
|
// go-keyring's Windows target is "service:user"; keyring's default differs.
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
@@ -199,30 +205,32 @@ impl Keychain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn available(&self) -> bool {
|
pub fn available(&self) -> bool {
|
||||||
let probe = "__nexus_keychain_probe__";
|
let service = self.service.clone();
|
||||||
entry(&self.service, probe).and_then(|e| e.set_password("probe").and_then(|_| e.delete_credential())).is_ok()
|
off_runtime(move || entry(&service, "__nexus_keychain_probe__").and_then(|e| e.set_password("probe").and_then(|_| e.delete_credential())).is_ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a secret; a miss under the current service falls back to the legacy one and copies
|
/// Reads a secret; a miss under the current service falls back to the legacy one and copies
|
||||||
/// the value forward. Legacy items are never deleted, so the old app keeps working.
|
/// the value forward. Legacy items are never deleted, so the old app keeps working.
|
||||||
pub fn get(&self, key: &str) -> Option<String> {
|
pub fn get(&self, key: &str) -> Option<String> {
|
||||||
if let Ok(v) = entry(&self.service, key).and_then(|e| e.get_password()) {
|
let (service, key2) = (self.service.clone(), key.to_string());
|
||||||
|
if let Ok(v) = off_runtime(move || entry(&service, &key2).and_then(|e| e.get_password())) {
|
||||||
return Some(v);
|
return Some(v);
|
||||||
}
|
}
|
||||||
let legacy = self.legacy.as_deref()?;
|
let (legacy, key2) = (self.legacy.clone()?, key.to_string());
|
||||||
let v = entry(legacy, key).and_then(|e| e.get_password()).ok()?;
|
let v = off_runtime(move || entry(&legacy, &key2).and_then(|e| e.get_password())).ok()?;
|
||||||
let _ = self.set(key, &v);
|
let _ = self.set(key, &v);
|
||||||
Some(v)
|
Some(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&self, key: &str, value: &str) -> Result<()> {
|
pub fn set(&self, key: &str, value: &str) -> Result<()> {
|
||||||
entry(&self.service, key)
|
let (service, key2, value) = (self.service.clone(), key.to_string(), value.to_string());
|
||||||
.and_then(|e| e.set_password(value))
|
off_runtime(move || entry(&service, &key2).and_then(|e| e.set_password(&value)))
|
||||||
.map_err(|e| format!("keychain: failed to store secret {key:?}: {e}").into())
|
.map_err(|e| format!("keychain: failed to store secret {key:?}: {e}").into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, key: &str) {
|
pub fn delete(&self, key: &str) {
|
||||||
let _ = entry(&self.service, key).and_then(|e| e.delete_credential());
|
let (service, key) = (self.service.clone(), key.to_string());
|
||||||
|
let _ = off_runtime(move || entry(&service, &key).and_then(|e| e.delete_credential()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user