soblendwa 0.1.0

SoblendWA: a blazing fast WhatsApp Web multi-device client library written in Rust. Crafted by Soblend Development Studio.
Documentation
use std::sync::Arc;
use std::time::Instant;

use super::{EncType, PreKeyBundle, SignalRepository};
use crate::binary::{self, Node};
use crate::crypto::KeyPair;
use crate::jid::Jid;
use crate::store::{Creds, MemoryStore};

fn peer() -> (SignalRepository, Creds) {
    let creds = Creds::generate();
    (SignalRepository::new(Arc::new(MemoryStore::new()), &creds), creds)
}

#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn throughput() {
    let (alice, _) = peer();
    let (bob, bob_creds) = peer();
    let one_time = KeyPair::generate();
    bob.store_pre_keys(&[(1, one_time.clone())]).await.unwrap();
    let bundle = PreKeyBundle {
        registration_id: bob_creds.registration_id,
        identity_key: bob_creds.identity_key.public,
        signed_pre_key_id: 1,
        signed_pre_key: bob_creds.signed_pre_key.key_pair.public,
        signed_pre_key_signature: bob_creds.signed_pre_key.signature,
        pre_key: Some((1, one_time.public)),
    };
    alice.inject_bundle("bob.0", &bundle).await.unwrap();
    let (_, first) = alice.encrypt("bob.0", b"hello").await.unwrap();
    bob.decrypt("alice.0", EncType::PreKey, &first).await.unwrap();
    let (_, ack) = bob.encrypt("alice.0", b"ack").await.unwrap();
    alice.decrypt("bob.0", EncType::Message, &ack).await.unwrap();

    let payload = vec![7u8; 120];
    let rounds = 20_000;
    let started = Instant::now();
    for _ in 0..rounds {
        let (kind, ct) = alice.encrypt("bob.0", &payload).await.unwrap();
        bob.decrypt("alice.0", kind, &ct).await.unwrap();
    }
    let elapsed = started.elapsed();
    println!("BENCH signal 1:1 encrypt+decrypt: {:.0} msg/s", rounds as f64 / elapsed.as_secs_f64());

    let started = Instant::now();
    for i in 0..rounds {
        let (ct, dist) = alice.group_encrypt("g@g.us", "alice.0", &payload).await.unwrap();
        if i == 0 {
            bob.process_distribution("g@g.us", "alice.0", &dist).await.unwrap();
        }
        bob.group_decrypt("g@g.us", "alice.0", &ct).await.unwrap();
    }
    let elapsed = started.elapsed();
    println!("BENCH signal group encrypt+decrypt: {:.0} msg/s", rounds as f64 / elapsed.as_secs_f64());

    let pair = KeyPair::generate();
    let other = KeyPair::generate();
    let started = Instant::now();
    for _ in 0..2000 {
        std::hint::black_box(pair.shared_secret(&other.public));
    }
    let dh = started.elapsed() / 2000;
    let signature = pair.sign(b"bench");
    let started = Instant::now();
    for _ in 0..2000 {
        std::hint::black_box(crate::crypto::verify(&pair.public, b"bench", &signature));
    }
    let verify = started.elapsed() / 2000;
    let started = Instant::now();
    for _ in 0..2000 {
        std::hint::black_box(KeyPair::generate());
    }
    let keygen = started.elapsed() / 2000;
    println!("BENCH x25519 {dh:?} · xeddsa verify {verify:?} · keygen {keygen:?}");

    let bundles: Vec<(String, PreKeyBundle)> = (0..1000u32)
        .map(|i| {
            let one_time = KeyPair::generate();
            let bundle = PreKeyBundle { pre_key: Some((i, one_time.public)), ..bundle.clone() };
            (format!("member{i}.0"), bundle)
        })
        .collect();
    let (sequential, _) = peer();
    let started = Instant::now();
    for (address, bundle) in &bundles {
        sequential.inject_bundles(vec![(address.clone(), bundle.clone())]).await;
    }
    let one_by_one = started.elapsed();
    let (parallel, _) = peer();
    let started = Instant::now();
    let results = parallel.inject_bundles(bundles).await;
    let batched = started.elapsed();
    assert!(results.iter().all(|(_, r)| r.is_ok()));
    println!("BENCH 1000 group sessions: one by one {one_by_one:?} · parallel {batched:?}");

    let stanza = Node::new("message")
        .attr("id", "3EB0A1B2C3D4E5F6A7B8C9")
        .attr("to", Jid::group("120363025246125486"))
        .attr("type", "text")
        .children((0..20).map(|i| {
            Node::new("to")
                .attr("jid", Jid::phone("5215512345678").with_device(i))
                .child_node(Node::new("enc").attr("v", "2").attr("type", "msg").bytes(vec![1u8; 140]))
        }));
    let rounds = 200_000;
    let started = Instant::now();
    let mut total = 0usize;
    for _ in 0..rounds {
        let encoded = binary::encode(&stanza);
        total += binary::decode_frame(&encoded).unwrap().nodes().len();
    }
    let elapsed = started.elapsed();
    assert_eq!(total, rounds * 20);
    println!(
        "BENCH binary encode+decode (21 nodes, {} bytes): {:.0} stanzas/s",
        binary::encode(&stanza).len(),
        rounds as f64 / elapsed.as_secs_f64()
    );
}