Russh - Async (tokio) SSH2 client and server rimplementation

Overview

Russh

Async (tokio) SSH2 client and server rimplementation.

This is a fork of Thrussh by Pierre-Étienne Meunier which adds:

  • More safety guarantees
  • AES256-GCM support
  • Dependency updates

Safety

  • deny(clippy::unwrap_used)
  • deny(clippy::expect_used)
  • deny(clippy::indexing_slicing)
  • deny(clippy::panic)
  • Exceptions are checked manually

Panics

  • When the Rust allocator fails to allocate memory during a CryptoVec being resized.

Unsafe code

  • cryptovec uses unsafe for faster copying, initialization and binding to native API.
  • russh-libsodium uses unsafe for libsodium bindings.
Comments
  • Native support for subsystems

    Native support for subsystems

    Would you like to add native support for subsystems such as SFTP and etc.? At the moment, the lib does not provide simple "out-of-the-box" implementations of server-side and client-side subsytems. I could take the time to integrate my ready implementation under russh, since I have already taken the time to implement the SFTP protocol on the server side. Are you interested in PR?

    opened by AspectUnk 16
  • Unable to connect to server with only public key authentication allowed

    Unable to connect to server with only public key authentication allowed

    Whenever I try to create an SSH server with only public key authentication allowed, I always get a Permission denied (publickey) message from ssh. I was able to reproduce the situation with the following code:

    use core::pin::Pin;
    use futures::FutureExt;
    use russh::{
        server::{self, Auth, Session},
        MethodSet,
    };
    use russh_keys::key::{KeyPair, PublicKey};
    use std::{net::SocketAddr, str::FromStr, sync::Arc};
    
    #[derive(Clone)]
    struct Server {}
    
    impl server::Server for Server {
        type Handler = Self;
    
        fn new_client(&mut self, socket_addr: Option<SocketAddr>) -> Self {
            self.to_owned()
        }
    }
    
    impl server::Handler for Server {
        type Error = russh::Error;
        type FutureAuth =
            Pin<Box<dyn core::future::Future<Output = Result<(Self, Auth), Self::Error>> + Send>>;
        type FutureUnit =
            Pin<Box<dyn core::future::Future<Output = Result<(Self, Session), Self::Error>> + Send>>;
        type FutureBool = Pin<
            Box<dyn core::future::Future<Output = Result<(Self, Session, bool), Self::Error>> + Send>,
        >;
    
        fn finished_auth(self, auth: Auth) -> Self::FutureAuth {
            async move { Ok((self, auth)) }.boxed()
        }
    
        fn finished_bool(self, b: bool, session: Session) -> Self::FutureBool {
            async move { Ok((self, session, b)) }.boxed()
        }
    
        fn finished(self, session: Session) -> Self::FutureUnit {
            async move { Ok((self, session)) }.boxed()
        }
    
        fn auth_publickey(self, user: &str, public_key: &PublicKey) -> Self::FutureAuth {
            async move { self.finished_auth(Auth::Accept).await }.boxed()
        }
    }
    
    #[tokio::main]
    async fn main() {
        let mut config = server::Config::default();
        config.methods = MethodSet::PUBLICKEY;
        config.keys.push(KeyPair::generate_ed25519().unwrap());
    
        server::run(
            Arc::new(config),
            &SocketAddr::from_str("0.0.0.0:2222").unwrap(),
            Server {},
        )
        .await
        .unwrap();
    }
    

    If I add a println!("AUTH PUBKEY BEING CALLED"); line right below the function declaration at fn auth_publickey(self, user: &str, public_key: &PublicKey), my terminal doesn't show any output, leading me to think the function is never even being called, but I can't figure out why that might be the case.

    opened by hwittenborn 7
  • Channel.window_size can overflow on add

    Channel.window_size can overflow on add

    Consider the following field:

    https://github.com/warp-tech/russh/blob/49bf9599637d492f0aa17377b9c66cf953cb0824/russh/src/channels.rs#L113

    This field is meant to indicate current window_size on the Channel.

    However, I've been able to hit a case where the u32 overflows on add, which doesn't seem correct. Here's the scenario:

    1. Push a large amount of data to an OpenSSH server
    2. Enter the send_data loop of the channel
    3. Eventually the window_size will become 0. When it does, we enter this loop to wait for an adjustment and reset the window_size.
    4. This continues until the data has all been pushed.
    5. After data returns, call Channel.wait in a loop waiting for ChannelMsg::Data to come through from the server.

    The issue I am seeing is that OpenSSH has sent a large amount of ChannelMsg::WindowAdjusted messages during step 4. send_data, does not attempt to process these, it only takes the first one it finds and leaves the rest in the channel. While waiting for ChannelMsg::Data in step 5, the remaining ChannelMsg::WindowAdjusted are read out of the channel first.

    Each CHANNEL_WINDOW_ADJUST coming from the server first goes through here:

    https://github.com/warp-tech/russh/blob/49bf9599637d492f0aa17377b9c66cf953cb0824/russh/src/client/encrypted.rs#L489-L505

    This code takes the existing Channel.recipient_window_size and adds the new adjustment amount to it. This resulting value is then passed into window_adjusted where it is used to build a new ChannelMsg::WindowAdjusted here:

    https://github.com/warp-tech/russh/blob/49bf9599637d492f0aa17377b9c66cf953cb0824/russh/src/client/mod.rs#L1450-L1465

    Finally, the wait method handles these messages by adding the entire Channel.recipient_window_size to the existing Channel.window_size here:

    https://github.com/warp-tech/russh/blob/49bf9599637d492f0aa17377b9c66cf953cb0824/russh/src/channels.rs#L347-L356

    Repeatedly adding the Channel.recipient_window_size to the Channel.window_size can overflow the u32 and cause a panic (in debug mode), or an two's complement wrapping (in release mode). I'm wondering if instead of self.window_size += new_size, we should do self.window_size = new_size since we are really passing in the value of Channel.recipient_window_size instead of some amount of byte adjustment from CHANNEL_WINDOW_ADJUST.

    opened by jgrund 6
  • Is Fork a four letter word?

    Is Fork a four letter word?

    Screenshot of https://www.reddit.com/r/rust/comments/u3s3m1/i_wrote_a_smarter_ssh_bastion_in_rust/

    afbeelding

    Idea behind this issue is to decide on the long term future of russh. There is no rush to do so.

    opened by stappersg 6
  • No disconnect handler when using exec_request

    No disconnect handler when using exec_request

    Behavior is different when using exec_request over a "normal" connection

    $ ssh localhost -p2222 test
    

    vs

    $ ssh localhost -p2222
    

    When adding the test parameter (which will be put into an exec_request) data isn't received per-keystroke anymore, but only once per new-line, and a ctrl+c on the client side doesn't trigger disconnects or channel close or anything.

    How or when do I know on the server when a client closed / disconnected the session when they supply an argument using exec_request?

    opened by AviiNL 5
  • Weird issue when sending a message from a server on an SSH connection

    Weird issue when sending a message from a server on an SSH connection

    I'm using this library's server portion, and the following code for the Handler trait is producing some weird output:

    fn shell_request(self, channel: ChannelId, mut session: Session) -> Self::FutureUnit {
        session.data(channel, "Interactive shell is disabled.\n".to_string().into());
        session.close(channel);
        self.finished(session)
    }
    

    For some reason this is showing the following output in my terminal when connecting to the server:

    Interactive shell is disabled.
                                  Connection to dev.hunterwittenborn.com closed.
    

    I'm not sure how related to the library this is, but any idea what might be causing it?

    opened by hwittenborn 4
  • At which point is it possible to send a prompt to the client in a Server implementation?

    At which point is it possible to send a prompt to the client in a Server implementation?

    Hi (again), I've tried to read the docs and the code but still failed in my intent. I'm implementing an honeypot server and I want to send a fake shell prompt (or any other string) to new clients as soon as they start the session, before they start sending commands.

    I've tried here since it's one of the few points where the Session object is mutable, but the client never displays it https://github.com/evilsocket/medusa/blob/main/src/protocols/ssh/handler.rs#L214 while it works fine in the data handler and/or after an exect_request.

    Thanks

    opened by evilsocket 4
  • initial support bidirectional, server-initiated channels

    initial support bidirectional, server-initiated channels

    Channels need not be exclusively client-created, in fact few (if any?) channels specify directionality, and refer only to a sender and receiver.

    This does an initial, somewhat minimal pass at making servers capable of initiating channels. This is rather rough and I think greater abstraction is warranted here; I pulled out relevant parsing code into parsing.rs, but there is logical duplication between the client and server which I don't have time to disentagle. This also only implements some channel functionality on the server-side, it's not as complete as the client.

    The significant breaking change here is that server::run_stream now returns (almost) immediatel with a SessionHandle, similar to the client; this is used to open channels.

    Ultimately this is a big set of changes I would understand if there's not great desire to merge this upstream :)

    opened by connor4312 4
  • Added test for ChannelStream & fix tests for windows

    Added test for ChannelStream & fix tests for windows

    ChannelStream works well for me. Unfortunately I don't have my macbook right now to run tests, but on windows I still noticed errors that I had to fix.

    opened by AspectUnk 3
  • Bump tokio from 1.17.0 to 1.19.2

    Bump tokio from 1.17.0 to 1.19.2

    Bumps tokio from 1.17.0 to 1.19.2.

    Release notes

    Sourced from tokio's releases.

    Tokio v1.19.2

    1.19.2 (June 6, 2022)

    This release fixes another bug in Notified::enable. (#4751)

    #4751: tokio-rs/tokio#4751

    Tokio v1.19.1

    1.19.1 (June 5, 2022)

    This release fixes a bug in Notified::enable. (#4747)

    #4747: tokio-rs/tokio#4747

    Tokio v1.19.0

    1.19.0 (June 3, 2022)

    Added

    • runtime: add is_finished method for JoinHandle and AbortHandle (#4709)
    • runtime: make global queue and event polling intervals configurable (#4671)
    • sync: add Notified::enable (#4705)
    • sync: add watch::Sender::send_if_modified (#4591)
    • sync: add resubscribe method to broadcast::Receiver (#4607)
    • net: add take_error to TcpSocket and TcpStream (#4739)

    Changed

    • io: refactor out usage of Weak in the io handle (#4656)

    Fixed

    • macros: avoid starvation in join! and try_join! (#4624)

    Documented

    • runtime: clarify semantics of tasks outliving block_on (#4729)
    • time: fix example for MissedTickBehavior::Burst (#4713)

    Unstable

    • metrics: correctly update atomics in IoDriverMetrics (#4725)
    • metrics: fix compilation with unstable, process, and rt, but without net (#4682)
    • task: add #[track_caller] to JoinSet/JoinMap (#4697)
    • task: add Builder::{spawn_on, spawn_local_on, spawn_blocking_on} (#4683)
    • task: add consume_budget for cooperative scheduling (#4498)
    • task: add join_set::Builder for configuring JoinSet tasks (#4687)
    • task: update return value of JoinSet::join_one (#4726)

    #4498: tokio-rs/tokio#4498 #4591: tokio-rs/tokio#4591 #4607: tokio-rs/tokio#4607 #4624: tokio-rs/tokio#4624 #4656: tokio-rs/tokio#4656 #4671: tokio-rs/tokio#4671 #4682: tokio-rs/tokio#4682 #4683: tokio-rs/tokio#4683 #4687: tokio-rs/tokio#4687

    ... (truncated)

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    opened by dependabot[bot] 3
  • "No common key algorithm" error when connect to ssh server

    Hello,

    I try the example code in async-ssh2-tokio (link), but failed with below error:

    Error: Other Error Happen

    Caused by: No common key algorithm

    The server is ok as I can ssh it by "ssh" command in linux, and if I try the same code to ssh a raspberry pi, it also works. I searched in google, and try to compare the difference :

    When check the key_exchange_init message in wireshark:

    for my unit: server_host_key_algorithms string: rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256

    for raspberry pi: server_host_key_algorithms string: rsa-sha2-512,rsa-sha2-256,ssh-rsa,ecdsa-sha2-nistp256,ssh-ed25519

    So it seems there is something different, does anyone know what was wrong? How can I fix it in the client side?

    Best Regards, Hermes

    bug 
    opened by ChenhuiZhang 3
  • Server Graceful Shutdown

    Server Graceful Shutdown

    Currently, russh doesn't provide a graceful shutdown mechanism. Graceful shutdowns are especially important in cloud environments to allow instances to be shut down without impacting users.

    The following should happen on shutdown:

    1. Stop listening accepting new connections
    2. Notify application code that a shutdown has been triggered
    3. Allow application code to notify that shutdown has been complete
    4. Wait for the existing connections to close
    5. If application code misbehaves and doesn't close the connection within a certain period of time, close the connection
    opened by tnewman 0
  • `channel_open_session` not returning

    `channel_open_session` not returning

    I have the following code for a Client:

    pub struct Client {
        handle: Handle<Handler>,
    }
    
    pub struct Channel {
        inner: russh::Channel<Msg>,
    }
    
    impl Client {
        pub async fn init(remote: &str, user: &str, key: impl AsRef<Path>) -> eyre::Result<Self> {
            let config = Arc::new(ClientConfig {
                connection_timeout: Some(Duration::from_secs(5)),
                ..Default::default()
            });
            let mut handle = client::connect(config, remote, Handler).await?;
            let key = Arc::new(load_secret_key(key, None)?);
            let res = handle.authenticate_publickey(user, key).await?;
            if !res {
                return Err(eyre::eyre!("Server rejected our SSH publickey"));
            }
            Ok(Self { handle })
        }
    
        pub async fn new_channel(&mut self) -> eyre::Result<Channel> {
            dbg!("a");
            let inner = self.handle.channel_open_session().await?;
            dbg!("b");
            Ok(Channel { inner })
        }
    }
    

    When running

        let mut ssh_client = ssh::Client::init(
            &env::var("REMOTE_ADDR")?,
            &env::var("SSH_USER")?,
            &env::var("SSH_KEY")?,
        )
        .await?;
        let mut channel = ssh_client.new_channel().await?;
    

    the "a" gets printed, but the "b" does not. I have implemented the method in the Handler for printing out the channel open confirmation, with the logs containing this:

    [snip]
    2022-12-19T11:02:19.625093Z DEBUG russh::client::encrypted: userauth_success    
    [src/ssh.rs:75] "a" = "a"
    2022-12-19T11:02:19.625233Z DEBUG russh::cipher: writing, seqn = 6    
    2022-12-19T11:02:19.625251Z DEBUG russh::cipher: padding length 7    
    2022-12-19T11:02:19.625259Z DEBUG russh::cipher: packet_length 32    
    2022-12-19T11:02:19.649032Z DEBUG russh::cipher: reading, len = [191, 235, 165, 118]    
    2022-12-19T11:02:19.649065Z DEBUG russh::cipher: reading, seqn = 7    
    2022-12-19T11:02:19.649152Z DEBUG russh::cipher: reading, clear len = 624    
    2022-12-19T11:02:19.649164Z DEBUG russh::cipher: read_exact 628    
    2022-12-19T11:02:19.649178Z DEBUG russh::cipher: read_exact done    
    2022-12-19T11:02:19.649533Z DEBUG russh::cipher: reading, padding_length 4    
    2022-12-19T11:02:19.649616Z  WARN russh::client::encrypted: Unhandled global request: Ok("[email protected]") 0    
    2022-12-19T11:02:19.649634Z DEBUG russh::cipher: writing, seqn = 7    
    2022-12-19T11:02:19.649642Z DEBUG russh::cipher: padding length 14    
    2022-12-19T11:02:19.649649Z DEBUG russh::cipher: packet_length 16    
    2022-12-19T11:02:19.649841Z DEBUG russh::cipher: reading, len = [131, 217, 96, 50]    
    2022-12-19T11:02:19.649864Z DEBUG russh::cipher: reading, seqn = 8    
    2022-12-19T11:02:19.649921Z DEBUG russh::cipher: reading, clear len = 136    
    2022-12-19T11:02:19.649929Z DEBUG russh::cipher: read_exact 140    
    2022-12-19T11:02:19.649938Z DEBUG russh::cipher: read_exact done    
    2022-12-19T11:02:19.650096Z DEBUG russh::cipher: reading, padding_length 7    
    2022-12-19T11:02:19.650120Z DEBUG russh::cipher: reading, len = [219, 50, 139, 30]    
    2022-12-19T11:02:19.650129Z DEBUG russh::cipher: reading, seqn = 9    
    2022-12-19T11:02:19.650200Z DEBUG russh::cipher: reading, clear len = 136    
    2022-12-19T11:02:19.650211Z DEBUG russh::cipher: read_exact 140    
    2022-12-19T11:02:19.650225Z DEBUG russh::cipher: read_exact done    
    2022-12-19T11:02:19.650451Z DEBUG russh::cipher: reading, padding_length 7    
    2022-12-19T11:02:19.672871Z DEBUG russh::cipher: reading, len = [89, 113, 103, 91]    
    2022-12-19T11:02:19.672897Z DEBUG russh::cipher: reading, seqn = 10    
    2022-12-19T11:02:19.672984Z DEBUG russh::cipher: reading, clear len = 40    
    2022-12-19T11:02:19.672995Z DEBUG russh::cipher: read_exact 44    
    2022-12-19T11:02:19.673010Z DEBUG russh::cipher: read_exact done    
    2022-12-19T11:02:19.673173Z DEBUG russh::cipher: reading, padding_length 6    
    2022-12-19T11:02:19.673207Z DEBUG russh::client::encrypted: channel_open_confirmation    
    2022-12-19T11:02:19.673251Z DEBUG ssh_worker::ssh: Got channel open confirmation for channel ChannelId(2)
    

    After this the program seems to freeze. I tried connecting to both a remote Debian server as well as a local linuxserver/openssh-server docker container. Anyone know what might be going wrong here?

    opened by netthier 7
  • client channel extended data AsyncReader

    client channel extended data AsyncReader

    Hi,

    I'm really liking the new async trait handlers!

    I can turn a channel into a AsyncRead + AsyncWrite stream using Channel's into_stream() method. Could you also provide a method to stream a Channel's stderr? Possibly, it could be a AsyncReader for extended data for the channel, special cased for where the extended_code == 1.

    I'm using russh-0.35.0-beta.8 Thanks

    enhancement 
    opened by mllken 0
  • Implement detached signal sender

    Implement detached signal sender

    Allow signals to be sent detached from the channel struct.

    This is done using a higher-order function and by cloning the sender so it can be used independently of the Channel.

    This is useful to be able to spawn a cancelation handler separately from the main Channel recieve loop.

    Signed-off-by: Joe Grund [email protected]

    opened by jgrund 5
  • add flags for openssl-only crypto

    add flags for openssl-only crypto

    Fixes #50

    This introduces an on-by-default rs-crypto flag, which enables the existing Rust-based crypto libraries (including aes and ED25519). However, these implementations can be removed by disabling the flag. If it's disabled, then openssl (when turned on) will stand in for them, in a less performant way.

    Note that while OpenSSL 3.x does have some ED25519 support, I have not done the work to make that compatible as well--partly because ED25519 is not yet an approved algorithm for my company to use, and partly to retain compatibility with OpenSSL 1.x

    opened by connor4312 4
  • FIPS

    FIPS "OpenSSL only" mode

    One cloud on the horizon for our usage of this library is the likelihood of needing to be FIPS certifiable. Long story short, this essentially means having all cryptography done by a dynamically linked OpenSSL library--which in turn is FIPS certified. In this case, we would use OpenSSL for the cryptography currently done by crates like aes and pbkdf2.

    I will (attempt to) implement this in our fork of the library, but I was wondering if it's something there'd be interest in merging upstream as a feature flag. Understanding that dependency on OpenSSL is not greatly desirable and that this would increase the complexity of the library for something a minority of consumers will ever need 😛

    opened by connor4312 4
Owner
Warptech Industries
A very serious intergalactic travel business
Warptech Industries
⚡ Blazing fast async/await HTTP client for Python written on Rust using reqwests

Reqsnaked Reqsnaked is a blazing fast async/await HTTP client for Python written on Rust using reqwests. Works 15% faster than aiohttp on average RAII

Yan Kurbatov 8 Mar 2, 2023
Concurrent and multi-stage data ingestion and data processing with Rust+Tokio

TokioSky Build concurrent and multi-stage data ingestion and data processing pipelines with Rust+Tokio. TokioSky allows developers to consume data eff

DanyalMh 29 Dec 11, 2022
Cornucopia is a small CLI utility resting on tokio-postgres and designed to facilitate PostgreSQL workflows in Rust

Cornucopia Generate type checked Rust from your SQL Install | Example Cornucopia is a small CLI utility resting on tokio-postgres and designed to faci

Louis Gariépy 1 Dec 20, 2022
Background task processing for Rust applications with Tokio, Diesel, and PostgreSQL.

Async persistent background task processing for Rust applications with Tokio. Queue asynchronous tasks to be processed by workers. It's designed to be

Rafael Carício 22 Mar 27, 2023
REC2 (Rusty External Command and Control) is client and server tool allowing auditor to execute command from VirusTotal and Mastodon APIs written in Rust. 🦀

Information: REC2 is an old personal project (early 2023) that I didn't continue development on. It's part of a list of projects that helped me to lea

Quentin Texier (g0h4n) 104 Oct 7, 2023
Services Info Register/KeepAlive/Publish/Subscribe. Based on etcd-rs, tokio

Services Info Register/KeepAlive/Publish/Subscribe. Based on etcd-rs, tokio

Mutalisk 5 Oct 4, 2022
A Rust CLI tool that helps you enforce Git policies through Git hooks both server and client side

GitPolicyEnforcer This is a command line utility written in Rust, that helps you utilize Git hooks, to enforce various policies. It currently supports

Vagelis Prokopiou 4 Aug 14, 2022
A backend server and client for Norg related applications.

Norgopolis Norgopolis is a lightweight communication, launcher and utility services client for the Neorg rust-native modules ecosystem on Desktop. It

Neorg 10 May 27, 2023
TinyTodo is a Cedar Agent example, with a server in Rust and client in python

TinyTodo - OPAL and Cedar Agent Demo TinyTodo is a simple application for managing task lists. It uses OPAL and Cedar Agent to control who has access

Permit.io 4 Aug 9, 2023
Bruteforce connecting to a specific Sea of Thieves server. Useful if you want to be in the same server as your friends.

SoT Server Finder Find which Sea of Thieves server you're connected to. Useful if you want to be in the same server as your friends. Setup Download so

Martin 4 Mar 19, 2023
Leptos server signals synced through Server-Sent-Events (SSE)

Leptos Server Sent Events Server signals are leptos signals kept in sync with the server through server-sent-events (SSE). The signals are read-only o

messense 13 Oct 3, 2023
A light-as-air client/server networking library for Rust

aeronet A light-as-air client/server networking library with first-class support for Bevy, providing a consistent API which can be implemented by diff

null 10 Nov 2, 2023
SA-MP client server list fix but written in Rust

Server List Fix This is a samp client server list fix, which reroutes the client's request to list.sa-mp.com to sam.markski.ar. The idea is originally

__SyS__ 4 Feb 4, 2024
Provides a mock Ambi client that emulates real sensor hardware such as an Edge client

ambi_mock_client Provides a mock Ambi client that emulates real sensor hardware such as an Edge client. Usage You must have Rust installed to build am

Rust Never Sleeps 2 Apr 1, 2022
A minimal readline with multiline and async support

RustyLine Async A minimal readline with multiline and async support. Inspired by rustyline , async-readline & termion-async-input.

Zyansheep 16 Dec 15, 2022
A simple Rust library for OpenAI API, free from complex async operations and redundant dependencies.

OpenAI API for Rust A community-maintained library provides a simple and convenient way to interact with the OpenAI API. No complex async and redundan

null 6 Apr 4, 2023
Fully-typed, async, reusable state management and synchronization for Dioxus 🧬

dioxus-query ?? ⚡ Fully-typed, async, reusable state management and synchronization for Dioxus ??. Inspired by TanStack Query. ⚠️ Work in progress ⚠️

Marc Espín 9 Aug 7, 2023
A parser combinator that is fully statically dispatched and supports both sync/async.

XParse A parser combinator that is fully statically dispatched and supports both sync & async parsing. How to use An example of a very simple JSON par

Alsein 4 Nov 27, 2023
An async autocompletion framework for Neovim

⚡ nvim-compleet This plugin is still in early development. ?? Table of Contents Installation Features Configuration Sources Commands Mappings Colors R

Riccardo Mazzarini 520 Dec 25, 2022