From 9a25292c9a193f3041aba945551229d862b3bc6e Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Wed, 19 Mar 2025 17:53:10 +0530 Subject: [PATCH] feat: added basic ui --- Cargo.lock | 446 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 + src/main.rs | 308 +++++++++++++++++++++++++++++------- 3 files changed, 702 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9e415d..5a09f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,6 +61,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -126,6 +132,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + [[package]] name = "argon2" version = "0.5.3" @@ -243,6 +255,21 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cbc" version = "0.1.2" @@ -314,6 +341,20 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -344,6 +385,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -403,6 +469,41 @@ dependencies = [ "syn", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.8.0" @@ -482,6 +583,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -538,6 +645,22 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "ff" version = "0.13.1" @@ -564,6 +687,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "futures" version = "0.3.31" @@ -704,6 +839,23 @@ dependencies = [ "subtle", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" @@ -766,6 +918,18 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + [[package]] name = "inout" version = "0.1.4" @@ -776,6 +940,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instability" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "internal-russh-forked-ssh-key" version = "0.6.10+upstream-0.6.7" @@ -810,6 +987,21 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "jiff" version = "0.2.4" @@ -865,12 +1057,48 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + [[package]] name = "md5" version = "0.7.0" @@ -899,6 +1127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.52.0", ] @@ -973,6 +1202,12 @@ dependencies = [ "libm", ] +[[package]] +name = "numtoa" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" + [[package]] name = "object" version = "0.36.7" @@ -1048,6 +1283,29 @@ dependencies = [ "windows", ] +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -1059,6 +1317,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -1197,11 +1461,14 @@ dependencies = [ name = "publik" version = "0.1.0" dependencies = [ + "anyhow", "env_logger", "log", + "ratatui", "russh", "thiserror 2.0.12", "tokio", + "tui-textarea", ] [[package]] @@ -1243,6 +1510,43 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "termion", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_termios" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" + [[package]] name = "regex" version = "1.11.1" @@ -1407,12 +1711,31 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "salsa20" version = "0.10.2" @@ -1422,6 +1745,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "scrypt" version = "0.11.0" @@ -1501,6 +1830,36 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -1581,6 +1940,40 @@ dependencies = [ "sha2", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -1598,6 +1991,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termion" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f359c854fbecc1ea65bc3683f1dcb2dce78b174a1ca7fda37acd1fff81df6ff" +dependencies = [ + "libc", + "libredox", + "numtoa", + "redox_termios", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1665,6 +2070,18 @@ dependencies = [ "syn", ] +[[package]] +name = "tui-textarea" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae" +dependencies = [ + "crossterm", + "ratatui", + "termion", + "unicode-width 0.2.0", +] + [[package]] name = "typenum" version = "1.18.0" @@ -1677,6 +2094,35 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "universal-hash" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index a768f61..2e3bfe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,11 @@ version = "0.1.0" edition = "2024" [dependencies] +anyhow = "1.0.97" env_logger = "0.11.7" log = "0.4.26" +ratatui = "0.29.0" russh = "0.51.1" thiserror = "2.0.12" tokio = "1.44.1" +tui-textarea = { version = "0.7.0", features = ["termion"] } diff --git a/src/main.rs b/src/main.rs index 961abbe..36f491a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,54 +1,27 @@ use std::collections::{HashMap, HashSet}; +use std::fmt::format; use std::path::Path; use std::sync::Arc; use authfile::Entity; +// use crossterm::event::{Event, read}; +use ratatui::backend::CrosstermBackend; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; +use ratatui::style::{Color, Style}; +use ratatui::termion::event::Event; +use ratatui::text::Text; +use ratatui::widgets::{Block, Borders, Clear, List, Paragraph}; +use ratatui::{Terminal, TerminalOptions, Viewport}; +use russh::keys::ssh_key; use russh::keys::{PublicKey, ssh_key::public::KeyData, ssh_key::rand_core::OsRng}; -use russh::server::{self, Auth, Handle, Msg, Server as _, Session}; -use russh::{Channel, ChannelId, CryptoVec}; +use russh::server::{self, Auth, Config, Handle, Handler, Msg, Server, Session}; +use russh::{Channel, ChannelId, CryptoVec, Pty}; use tokio::sync::Mutex; +use tokio::sync::mpsc::{UnboundedSender, unbounded_channel}; +use tui_textarea::TextArea; mod authfile; -#[tokio::main] -async fn main() { - env_logger::builder() - .filter_level(log::LevelFilter::Debug) - .init(); - - let mut methods = russh::MethodSet::empty(); - methods.push(russh::MethodKind::PublicKey); - - let keychain = authfile::read(Path::new("./authfile")).await.unwrap(); - let key_data_pool = new_atomic(build_key_data_pool(&keychain)); - let key_data_to_id = new_atomic(HashMap::new()); - let id_to_user = new_atomic(HashMap::new()); - let clients = new_atomic(HashMap::new()); - let key_data_to_user = new_atomic(keychain.iter().map(|e| (e.key_data(), e.clone())).collect()); - let keychain = new_atomic(keychain); - - let config = russh::server::Config { - inactivity_timeout: Some(std::time::Duration::from_secs(3600)), - auth_rejection_time: std::time::Duration::from_secs(3), - auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)), - methods, - keys: vec![ - russh::keys::PrivateKey::random(&mut OsRng, russh::keys::Algorithm::Ed25519).unwrap(), - ], - ..Default::default() - }; - let config = Arc::new(config); - - let mut sh = Server { - keychain, - id_to_user, - key_data_to_id, - key_data_pool, - key_data_to_user, - clients, - id: 0, - }; - sh.run_on_address(config, ("0.0.0.0", 2222)).await.unwrap(); -} +type SshTerminal = Terminal>; fn build_key_data_pool(entities: &[Arc]) -> HashSet { entities.iter().map(|e| e.key_data()).collect() @@ -62,6 +35,12 @@ fn new_atomic(object: T) -> Atomic { type Atomic = Arc>; +#[derive(Default)] +struct App { + pub history: Vec, + pub counter: usize, +} + #[derive(thiserror::Error, Debug)] pub enum Error { #[error("failed to disconnect client with id {0}")] @@ -75,11 +54,58 @@ pub enum Error { pub struct Client { channel: ChannelId, handle: Handle, + terminal: SshTerminal, + textarea: TextArea<'static>, // entity: Arc, } +struct TerminalHandle { + sender: UnboundedSender>, + // The sink collects the data which is finally sent to sender. + sink: Vec, +} + +impl TerminalHandle { + async fn start(handle: Handle, channel_id: ChannelId) -> Self { + let (sender, mut receiver) = unbounded_channel::>(); + tokio::spawn(async move { + while let Some(data) = receiver.recv().await { + let result = handle.data(channel_id, data.into()).await; + if result.is_err() { + eprintln!("Failed to send data: {:?}", result); + } + } + }); + Self { + sender, + sink: Vec::new(), + } + } +} + +// The crossterm backend writes to the terminal handle. +impl std::io::Write for TerminalHandle { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.sink.extend_from_slice(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + let result = self.sender.send(self.sink.clone()); + if result.is_err() { + return Err(std::io::Error::new( + std::io::ErrorKind::BrokenPipe, + result.unwrap_err(), + )); + } + + self.sink.clear(); + Ok(()) + } +} + #[derive(Clone)] -struct Server { +struct AppServer { keychain: Atomic>>, key_data_pool: Atomic>, key_data_to_user: Atomic>>, @@ -88,9 +114,32 @@ struct Server { clients: Atomic>, id: usize, + app: Atomic, } -impl Server { +impl AppServer { + pub async fn run(&mut self) -> Result<(), anyhow::Error> { + let app = self.app.clone(); + let clients = self.clients.clone(); + let mut methods = russh::MethodSet::empty(); + methods.push(russh::MethodKind::PublicKey); + + let config = russh::server::Config { + inactivity_timeout: Some(std::time::Duration::from_secs(3600)), + auth_rejection_time: std::time::Duration::from_secs(3), + auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)), + methods, + keys: vec![ + russh::keys::PrivateKey::random(&mut OsRng, russh::keys::Algorithm::Ed25519) + .unwrap(), + ], + ..Default::default() + }; + self.run_on_address(Arc::new(config), ("0.0.0.0", 2222)) + .await?; + Ok(()) + } + async fn reload(&mut self) -> Result<(), Error> { let new_keychain = authfile::read(Path::new("./authfile")).await?; let new_key_data_pool = build_key_data_pool(&new_keychain); @@ -156,7 +205,7 @@ impl Server { } } -impl server::Server for Server { +impl Server for AppServer { type Handler = Self; fn new_client(&mut self, _: Option) -> Self { let s = self.clone(); @@ -168,7 +217,7 @@ impl server::Server for Server { } } -impl server::Handler for Server { +impl Handler for AppServer { type Error = Error; async fn channel_open_session( @@ -180,14 +229,25 @@ impl server::Handler for Server { // let entity = self.entity().await; let channel = channel.id(); let handle = session.handle(); + let terminal_handle = TerminalHandle::start(handle.clone(), channel.clone()).await; + + let backend = CrosstermBackend::new(terminal_handle); + + // the correct viewport area will be set when the client request a pty + let options = TerminalOptions { + viewport: Viewport::Fixed(Rect::default()), + }; + + let terminal = Terminal::with_options(backend, options).unwrap(); let mut clients = self.clients.lock().await; clients.insert( self.id, Client { + textarea: TextArea::default(), channel, handle, - // entity, + terminal, }, ); } @@ -225,23 +285,134 @@ impl server::Handler for Server { return Err(russh::Error::Disconnect.into()); } + // Alt+Return + if data == [27, 13] { + let text = self + .clients + .lock() + .await + .get_mut(&self.id) + .unwrap() + .textarea + .lines() + .to_vec() + .join("\n"); + + let name = self.entity().await.name().to_string(); + let message = format!("[{name}]: {text}"); + self.app.lock().await.history.push(message); + } + + if !data.is_empty() { + let mut iterator = data.iter().skip(1).map(|d| Ok(*d)); + let keycode = ratatui::termion::event::parse_event(data[0], &mut iterator).unwrap(); + self.clients + .lock() + .await + .get_mut(&self.id) + .unwrap() + .textarea + .input(keycode); + } + // Press `r` to reload the authorization file if data == [114] { self.reload().await?; } - let data = CryptoVec::from(format!( - "[{}]: {}\r\n", - self.entity().await.name(), - String::from_utf8_lossy(data) - )); - self.post(data.clone()).await; - session.data(channel, data)?; + let clients = self.clients.clone(); + let history: Vec = self + .app + .lock() + .await + .history + .iter() + .rev() + .take(20) + .cloned() + .collect(); + tokio::spawn(async move { + for ( + _, + Client { + terminal, textarea, .. + }, + ) in clients.lock().await.iter_mut() + { + terminal + .draw(|f| { + // clear the screen + let area = f.area(); + f.render_widget(Clear, area); + + // split vertically as 80-20 + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Percentage(80), + Constraint::Percentage(20), + ]) + .split(f.area()); + let style = Style::default().fg(Color::Green); + + let paragraphs: Vec<_> = history + .iter() + .map(|message| Text::styled(message.to_string(), style)) + .collect(); + + let paragraphs = + List::new(paragraphs).block(Block::bordered().title("chat")); + + // let block = Block::default() + // .title("chat") + // .borders(Borders::ALL); + + // f.render_widget(paragraph.block(block), area); + f.render_widget(paragraphs, layout[0]); + f.render_widget(&*textarea, layout[1]); + }) + .unwrap(); + } + }); + // let data = CryptoVec::from(format!( + // "[{}]: {}\r\n", + // self.entity().await.name(), + // String::from_utf8_lossy(data) + // )); + // self.post(data.clone()).await; + // session.data(channel, data)?; + Ok(()) + } + + async fn pty_request( + &mut self, + channel: ChannelId, + _: &str, + col_width: u32, + row_height: u32, + _: u32, + _: u32, + _: &[(Pty, u32)], + session: &mut Session, + ) -> Result<(), Self::Error> { + let rect = Rect { + x: 0, + y: 0, + width: col_width as u16, + height: row_height as u16, + }; + + let mut clients = self.clients.lock().await; + let client = clients.get_mut(&self.id).unwrap(); + client.terminal.resize(rect).unwrap(); + + session.channel_success(channel)?; + Ok(()) } } -impl Drop for Server { +impl Drop for AppServer { fn drop(&mut self) { let id = self.id; let clients = self.clients.clone(); @@ -251,3 +422,30 @@ impl Drop for Server { }); } } + +#[tokio::main] +async fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .init(); + + let keychain = authfile::read(Path::new("./authfile")).await.unwrap(); + let key_data_pool = new_atomic(build_key_data_pool(&keychain)); + let key_data_to_id = new_atomic(HashMap::new()); + let id_to_user = new_atomic(HashMap::new()); + let clients = new_atomic(HashMap::new()); + let key_data_to_user = new_atomic(keychain.iter().map(|e| (e.key_data(), e.clone())).collect()); + let keychain = new_atomic(keychain); + + let mut sh = AppServer { + app: new_atomic(App::default()), + keychain, + id_to_user, + key_data_to_id, + key_data_pool, + key_data_to_user, + clients, + id: 0, + }; + sh.run().await.unwrap(); +}