use core::str;
use std::{
    convert::TryInto,
    net::{IpAddr, Ipv6Addr, SocketAddr},
    num::ParseIntError,
    str::FromStr,
    sync::Arc,
};

use anyhow::{Context, Result};
use bytes::Bytes;
use clap::Parser;
use quinn::crypto::rustls::QuicClientConfig;
use rustls::{
    RootCertStore,
    pki_types::{CertificateDer, PrivateKeyDer},
};
use tokio::runtime::{Builder, Runtime};
use tracing::trace;

pub mod stats;

pub fn configure_tracing_subscriber() {
    tracing::subscriber::set_global_default(
        tracing_subscriber::FmtSubscriber::builder()
            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
            .finish(),
    )
    .unwrap();
}

/// Creates a server endpoint which runs on the given runtime
pub fn server_endpoint(
    rt: &tokio::runtime::Runtime,
    cert: CertificateDer<'static>,
    key: PrivateKeyDer<'static>,
    opt: &Opt,
) -> (SocketAddr, quinn::Endpoint) {
    let cert_chain = vec![cert];
    let mut server_config = quinn::ServerConfig::with_single_cert(cert_chain, key).unwrap();
    server_config.transport = Arc::new(transport_config(opt));

    let endpoint = {
        let _guard = rt.enter();
        quinn::Endpoint::server(
            server_config,
            SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0),
        )
        .unwrap()
    };
    let server_addr = endpoint.local_addr().unwrap();
    (server_addr, endpoint)
}

/// Create a client endpoint and client connection
pub async fn connect_client(
    server_addr: SocketAddr,
    server_cert: CertificateDer<'_>,
    opt: Opt,
) -> Result<(quinn::Endpoint, quinn::Connection)> {
    let endpoint =
        quinn::Endpoint::client(SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0)).unwrap();

    let mut roots = RootCertStore::empty();
    roots.add(server_cert)?;

    let default_provider = rustls::crypto::ring::default_provider();
    let provider = rustls::crypto::CryptoProvider {
        cipher_suites: vec![opt.cipher.as_rustls()],
        ..default_provider
    };

    let crypto = rustls::ClientConfig::builder_with_provider(provider.into())
        .with_protocol_versions(&[&rustls::version::TLS13])
        .unwrap()
        .with_root_certificates(roots)
        .with_no_client_auth();

    let mut client_config = quinn::ClientConfig::new(Arc::new(QuicClientConfig::try_from(crypto)?));
    client_config.transport_config(Arc::new(transport_config(&opt)));

    let connection = endpoint
        .connect_with(client_config, server_addr, "localhost")
        .unwrap()
        .await
        .context("unable to connect")?;
    trace!("connected");

    Ok((endpoint, connection))
}

pub async fn drain_stream(mut stream: quinn::RecvStream, read_unordered: bool) -> Result<usize> {
    let mut read = 0;

    if read_unordered {
        let mut stream = stream.into_unordered();
        while let Some(chunk) = stream.read_chunk(usize::MAX).await? {
            read += chunk.bytes.len();
        }
    } else {
        // These are 32 buffers, for reading approximately 32kB at once
        #[rustfmt::skip]
        let mut bufs = [
            Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
            Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
            Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
            Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
            Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
            Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
            Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
            Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
        ];

        while let Some(n) = stream.read_chunks(&mut bufs[..]).await? {
            read += bufs.iter().take(n).map(|buf| buf.len()).sum::<usize>();
        }
    }

    Ok(read)
}

pub async fn send_data_on_stream(stream: &mut quinn::SendStream, stream_size: u64) -> Result<()> {
    const DATA: &[u8] = &[0xAB; 1024 * 1024];
    let bytes_data = Bytes::from_static(DATA);

    let full_chunks = stream_size / (DATA.len() as u64);
    let remaining = (stream_size % (DATA.len() as u64)) as usize;

    for _ in 0..full_chunks {
        stream
            .write_chunk(bytes_data.clone())
            .await
            .context("failed sending data")?;
    }

    if remaining != 0 {
        stream
            .write_chunk(bytes_data.slice(0..remaining))
            .await
            .context("failed sending data")?;
    }

    stream.finish().unwrap();
    // Wait for stream to close
    _ = stream.stopped().await;

    Ok(())
}

pub fn rt() -> Runtime {
    Builder::new_current_thread().enable_all().build().unwrap()
}

pub fn transport_config(opt: &Opt) -> quinn::TransportConfig {
    // High stream windows are chosen because the amount of concurrent streams
    // is configurable as a parameter.
    let mut config = quinn::TransportConfig::default();
    config.max_concurrent_uni_streams(opt.max_streams.try_into().unwrap());
    config.initial_mtu(opt.initial_mtu);

    let mut acks = quinn::AckFrequencyConfig::default();
    acks.ack_eliciting_threshold(10u32.into());
    config.ack_frequency_config(Some(acks));

    config
}

#[derive(Parser, Debug, Clone, Copy)]
#[clap(name = "bulk")]
pub struct Opt {
    /// The total number of clients which should be created
    #[clap(long = "clients", short = 'c', default_value = "1")]
    pub clients: usize,
    /// The total number of streams which should be created
    #[clap(long = "streams", short = 'n', default_value = "1")]
    pub streams: usize,
    /// The amount of concurrent streams which should be used
    #[clap(long = "max_streams", short = 'm', default_value = "1")]
    pub max_streams: usize,
    /// Number of bytes to transmit from server to client
    ///
    /// This can use SI suffixes for sizes. For example, 1M will transfer
    /// 1MiB, 10G will transfer 10GiB.
    #[clap(long, default_value = "1G", value_parser = parse_byte_size)]
    pub download_size: u64,
    /// Number of bytes to transmit from client to server
    ///
    /// This can use SI suffixes for sizes. For example, 1M will transfer
    /// 1MiB, 10G will transfer 10GiB.
    #[clap(long, default_value = "0", value_parser = parse_byte_size)]
    pub upload_size: u64,
    /// Show connection stats the at the end of the benchmark
    #[clap(long = "stats")]
    pub stats: bool,
    /// Whether to use the unordered read API
    #[clap(long = "unordered")]
    pub read_unordered: bool,
    /// Allows to configure the desired cipher suite
    ///
    /// Valid options are: aes128, aes256, chacha20
    #[clap(long = "cipher", default_value = "aes128")]
    pub cipher: CipherSuite,
    /// Starting guess for maximum UDP payload size
    #[clap(long, default_value = "1200")]
    pub initial_mtu: u16,
}

fn parse_byte_size(s: &str) -> Result<u64, ParseIntError> {
    let s = s.trim();

    let multiplier = match s.chars().last() {
        Some('T') => 1024 * 1024 * 1024 * 1024,
        Some('G') => 1024 * 1024 * 1024,
        Some('M') => 1024 * 1024,
        Some('k') => 1024,
        _ => 1,
    };

    let s = match multiplier {
        1 => s,
        _ => &s[..s.len() - 1],
    };

    Ok(u64::from_str(s)? * multiplier)
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CipherSuite {
    Aes128,
    Aes256,
    Chacha20,
}

impl CipherSuite {
    pub fn as_rustls(self) -> rustls::SupportedCipherSuite {
        use rustls::crypto::ring::cipher_suite;
        match self {
            Self::Aes128 => cipher_suite::TLS13_AES_128_GCM_SHA256,
            Self::Aes256 => cipher_suite::TLS13_AES_256_GCM_SHA384,
            Self::Chacha20 => cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
        }
    }
}

impl FromStr for CipherSuite {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "aes128" => Ok(Self::Aes128),
            "aes256" => Ok(Self::Aes256),
            "chacha20" => Ok(Self::Chacha20),
            _ => Err(anyhow::anyhow!("Unknown cipher suite {}", s)),
        }
    }
}

Homonyms

soft3/mir/src/lib.rs
soft3/strata/src/lib.rs
soft3/lens/src/lib.rs
soft3/crate/src/lib.rs
cyb/prysm/rs/lib.rs
cyb/honeycrisp/src/lib.rs
soft3/mudra/src/lib.rs
soft3/cybergraph/src/lib.rs
warriors/trisha/wgpu/lib.rs
soft3/glia/run/lib.rs
soft3/nox/rs/lib.rs
warriors/trisha/rs/lib.rs
soft3/tru/rs/lib.rs
cyb/optica/src/lib.rs
warriors/trisha/honeycrisp/lib.rs
soft3/foculus/src/lib.rs
cyb/core/src/lib.rs
soft3/glia/import/lib.rs
cyb/shell/src/lib.rs
neural/trident/src/lib.rs
neural/rune/rs/ast/lib.rs
cyb/crates/cyb/src/lib.rs
neural/rs/dialect/src/lib.rs
neural/rune/rs/compile/lib.rs
neural/rs/link/src/lib.rs
neural/rune/rs/lex/lib.rs
soft3/hemera/rs/src/lib.rs
soft3/radio/iroh/src/lib.rs
soft3/lens/core/src/lib.rs
cyb/honeycrisp/unimem/src/lib.rs
soft3/radio/iroh-ffi/src/lib.rs
soft3/lens/brakedown/src/lib.rs
soft3/lens/binius/src/lib.rs
neural/rune/rs/interp/lib.rs
soft3/radio/iroh-blobs/src/lib.rs
neural/rune/rs/parse/lib.rs
neural/rune/rs/mold/lib.rs
neural/rune/rs/subject/lib.rs
soft3/strata/proof/src/lib.rs
soft3/lens/ikat/src/lib.rs
soft3/radio/iroh-docs/src/lib.rs
soft3/radio/cyber-bao/src/lib.rs
cyb/honeycrisp/acpu/src/lib.rs
neural/eidos/rs/src/lib.rs
soft3/hemera/wgsl/src/lib.rs
soft3/lens/porphyry/src/lib.rs
neural/rs/macros/src/lib.rs
soft3/bbg/rs/src/lib.rs
cyb/honeycrisp/rane/src/lib.rs
soft3/radio/iroh-base/src/lib.rs
neural/rune/rs/parse-pure/lib.rs
neural/rs/mir-format/src/lib.rs
soft3/zheng/rs/src/lib.rs
soft3/lens/assayer/src/lib.rs
neural/rune/rs/prysm/lib.rs
soft3/strata/compute/src/lib.rs
soft3/tok/rs/src/lib.rs
soft3/strata/nebu/rs/lib.rs
soft3/radio/iroh-dns-server/src/lib.rs
soft3/conformance/rs/src/lib.rs
cyb/honeycrisp/aruminium/src/lib.rs
soft3/strata/kuro/rs/lib.rs
soft3/radio/iroh-car/src/lib.rs
soft3/radio/iroh-gossip/src/lib.rs
soft3/radio/iroh-willow/src/lib.rs
soft3/strata/ext/src/lib.rs
neural/rs/sigil/src/lib.rs
neural/rs/core/src/lib.rs
cyb/crates/cyb-reserve/src/lib.rs
soft3/radio/iroh-relay/src/lib.rs
neural/rs/darwin-sys/src/lib.rs
soft3/strata/core/src/lib.rs
neural/rs/codegen/src/lib.rs
neural/rune/rs/lower/lib.rs
cyb/wysm/crates/wasmi/src/lib.rs
soft3/radio/quinn/quinn/src/lib.rs
soft3/radio/quinn/quinn-proto/src/lib.rs
cyb/evy/forks/bevy_mesh/src/lib.rs
cyb/evy/crates/evy_diagnostic/src/lib.rs
cyb/evy/forks/bevy_anti_alias/src/lib.rs
neural/inf/rs/parse/src/lib.rs
neural/inf/rs/ast/src/lib.rs
cyb/evy/forks/bevy_post_process/src/lib.rs
soft3/radio/nettools/netwatch/src/lib.rs
neural/inf/rs/plan/src/lib.rs
cyb/evy/crates/evy_ecs_storage/src/lib.rs
soft3/strata/jali/rs/src/lib.rs
cyb/evy/crates/evy_engine_core/src/lib.rs
cyb/wysm/crates/collections/src/lib.rs
neural/inf/rs/oracle/src/lib.rs
cyb/evy/forks/bevy_sprite_render/src/lib.rs
cyb/wysm/crates/wast/src/lib.rs
cyb/evy/crates/evy_prysm_core/src/lib.rs
soft3/radio/quinn/quinn-udp/src/lib.rs
soft3/strata/kuro/wgsl/src/lib.rs
cyb/evy/forks/bevy_sprite/src/lib.rs
soft3/lytics/rs/core/src/lib.rs
cyb/wysm/crates/c_api/macro/lib.rs
cyb/evy/forks/bevy_gizmos/src/lib.rs
soft3/strata/nebu/wgsl/src/lib.rs
soft3/radio/nettools/portmapper/src/lib.rs
cyb/evy/forks/bevy_gizmos_render/src/lib.rs
cyb/evy/forks/bevy_transform/src/lib.rs
cyb/wysm/crates/wasi/src/lib.rs
soft3/radio/tests/integration/src/lib.rs
neural/trident/editor/zed/src/lib.rs
cyb/evy/forks/bevy_core_pipeline/src/lib.rs
cyb/evy/forks/bevy_pbr/src/lib.rs
cyb/evy/crates/evy_engine_dispatch/src/lib.rs
soft3/lytics/rs/event/src/lib.rs
cyb/evy/forks/bevy_ecs/src/lib.rs
cyb/evy/forks/bevy_diagnostic/src/lib.rs
neural/inf/rs/lex/src/lib.rs
neural/inf/rs/eval/src/lib.rs
cyb/evy/forks/naga/src/lib.rs
cyb/evy/crates/evy_platform_caps/src/lib.rs
soft3/strata/trop/wgsl/src/lib.rs
cyb/wysm/crates/core/src/lib.rs
cyb/wysm/crates/c_api/artifact/lib.rs
cyb/wysm/crates/fuzz/src/lib.rs
cyb/evy/forks/bevy_animation/src/lib.rs
soft3/strata/genies/wgsl/src/lib.rs
cyb/evy/crates/evy_dialect/src/lib.rs
soft3/tape/impl/rust/src/lib.rs
soft3/strata/trop/rs/src/lib.rs
cyb/evy/crates/evy_engine_tasks/src/lib.rs
neural/rs/tests/macro-integration/src/lib.rs
soft3/radio/quinn/perf/src/lib.rs
soft3/strata/genies/rs/src/lib.rs
cyb/evy/crates/evy_radio/src/lib.rs
soft3/radio/iroh/bench/src/lib.rs
cyb/wysm/crates/c_api/src/lib.rs
cyb/evy/forks/bevy_tasks/src/lib.rs
cyb/evy/forks/bevy_render/src/lib.rs
cyb/wysm/crates/ir/src/lib.rs
neural/inf/rs/source/src/lib.rs
soft3/radio/iroh-ffi/iroh-js/src/lib.rs
soft3/strata/jali/wgsl/src/lib.rs
neural/inf/rs/value/src/lib.rs
cyb/evy/forks/bevy_image/src/lib.rs
neural/inf/rs/lower/src/lib.rs
bootloader/go-cyber/cw/contracts/graph-filter/src/lib.rs
bootloader/go-cyber/cw/packages/cyber-std-test/src/lib.rs
bootloader/go-cyber/cw/contracts/std-test/src/lib.rs
bootloader/go-cyber/cw/packages/cyber-std/src/lib.rs

Graph