Skip to main content

Overview

WhatsApp-Rust is a high-performance, async Rust library for the WhatsApp Web API. The project follows a modular, layered architecture that separates protocol concerns from runtime concerns, enabling platform-agnostic core logic with pluggable backends.

Workspace Structure

The project is organized as a Cargo workspace with multiple crates:
whatsapp-rust/
├── src/                          # Main client library
├── wacore/                       # Platform-agnostic core
│   ├── binary/                   # WhatsApp binary protocol
│   ├── libsignal/                # Signal Protocol implementation
│   ├── appstate/                 # App state management
│   ├── noise/                    # Noise Protocol handshake
│   └── derive/                   # Derive macros
├── waproto/                      # Protocol Buffers definitions
├── storages/sqlite-storage/      # SQLite backend
├── transports/tokio-transport/   # Tokio WebSocket transport
└── http_clients/ureq-client/     # HTTP client for media

Three Main Crates

wacore - Platform-Agnostic Core

Location: wacore/ Purpose: Contains core logic for the WhatsApp binary protocol, cryptography primitives, IQ protocol types, and state management traits. Key Features:
  • No runtime dependencies on Tokio or specific databases
  • Pure protocol implementation
  • Cryptographic operations (Signal Protocol, Noise Protocol)
  • Type-safe protocol node builders
Key Modules:
wacore/
├── binary/           // Binary protocol encoding/decoding
├── libsignal/        // E2E encryption
├── noise/            // Noise Protocol handshake
├── appstate/         // App state sync protocol
├── iq/               // Type-safe IQ protocol types
├── protocol.rs       // ProtocolNode trait
├── types/
│   ├── events.rs     // Event definitions
│   └── message.rs    // Message types
└── store/
    ├── traits.rs     // Storage trait definitions
    └── device.rs     // Device state model

waproto - Protocol Buffers

Location: waproto/ Purpose: Houses WhatsApp’s Protocol Buffers definitions compiled to Rust structs. Build Process:
// build.rs uses prost to compile .proto files
prost_build::compile_protos(&["src/whatsapp.proto"], &["src/"])?;
Generated Types:
  • Message - All message types
  • WebMessageInfo - Message metadata
  • HistorySync - Chat history
  • SyncActionValue - App state mutations

whatsapp-rust - Main Client

Location: src/ Purpose: Integrates wacore with the Tokio runtime, provides high-level client API, and manages storage. Key Features:
  • Asynchronous operations with Tokio
  • SQLite persistence (pluggable)
  • Event bus system
  • Feature modules (groups, media, etc.)

Key Components

Client

Location: src/client.rs Purpose: Orchestrates connection lifecycle, event bus, and high-level operations.
pub struct Client {
    pub(crate) core: wacore::client::CoreClient,
    pub(crate) persistence_manager: Arc<PersistenceManager>,
    pub(crate) media_conn: Arc<RwLock<Option<MediaConn>>>,
    pub(crate) noise_socket: Arc<Mutex<Option<Arc<NoiseSocket>>>>,
    // ... connection state, caches, locks
}
Responsibilities:
  • Connection management
  • Request/response routing
  • Event dispatching
  • Session management

PersistenceManager

Location: src/store/persistence_manager.rs Purpose: Manages all state changes and persistence.
pub struct PersistenceManager {
    device: Arc<RwLock<Device>>,
    backend: Arc<dyn Backend>,
    dirty: Arc<AtomicBool>,
    save_notify: Arc<Notify>,
}
Critical Pattern:
  • Never modify Device state directly
  • Use DeviceCommand + process_command()
  • For read-only: get_device_snapshot()

Signal Protocol

Location: wacore/libsignal/ & src/store/signal*.rs Purpose: End-to-end encryption via Signal Protocol implementation. Features:
  • Double Ratchet algorithm
  • Pre-key bundles
  • Session management
  • Sender keys for groups

Socket & Handshake

Location: src/socket/, src/handshake.rs Purpose: WebSocket connection and Noise Protocol handshake. Flow:
  1. WebSocket connection
  2. Noise handshake (XX pattern)
  3. Encrypted frame exchange

Module Interactions

Layer Responsibilities

wacore Layer (Platform-Agnostic)

  • Protocol logic
  • State traits
  • Cryptographic helpers
  • Data models
Example: IQ Protocol
// wacore/src/iq/groups.rs
pub struct GroupQueryIq {
    group_jid: Jid,
}

impl IqSpec for GroupQueryIq {
    type Response = GroupInfoResponse;
    fn build_iq(&self) -> InfoQuery<'static> { /* ... */ }
    fn parse_response(&self, response: &Node) -> Result<Self::Response> { /* ... */ }
}

whatsapp-rust Layer (Runtime)

  • Runtime orchestration
  • Storage integration
  • User-facing API
Example: Feature API
// src/features/groups.rs
impl Client {
    pub async fn get_group_info(&self, jid: &Jid) -> Result<GroupInfoResponse> {
        self.execute(GroupQueryIq::new(jid)).await
    }
}

Protocol Entry Points

Incoming Messages

Flow: src/message.rs → Signal decryption → Event dispatch
// src/message.rs
pub async fn handle_message(client: &Arc<Client>, node: &Node) {
    // 1. Extract encrypted message
    // 2. Decrypt via Signal Protocol
    // 3. Dispatch Event::Message
}

Outgoing Messages

Flow: src/send.rs → Signal encryption → Socket send
// src/send.rs
pub async fn send_message(client: &Arc<Client>, msg: &Message) {
    // 1. Encrypt via Signal Protocol
    // 2. Build protocol node
    // 3. Send via NoiseSocket
}

Socket Communication

Flow: src/socket/ → Noise framing → Transport
// src/socket/mod.rs
impl NoiseSocket {
    pub async fn send_node(&self, node: Node) -> Result<()> {
        // 1. Marshal to binary
        // 2. Encrypt with Noise
        // 3. Frame and send
    }
}

Connection Lifecycle

Auto-Reconnection

The client implements robust reconnection handling:
// Client fields for reconnection management
pub enable_auto_reconnect: Arc<AtomicBool>,      // Toggle auto-reconnect
pub auto_reconnect_errors: Arc<AtomicU32>,        // Error count for backoff
pub(crate) expected_disconnect: Arc<AtomicBool>,  // Expected vs unexpected
pub(crate) connection_generation: Arc<AtomicU64>, // Detect stale tasks
Reconnection Flow:
  1. Connection lost → cleanup_connection_state()
  2. Check enable_auto_reconnect → exit if disabled
  3. Check expected_disconnect → immediate reconnect if expected (e.g., 515)
  4. Calculate backoff delay (2s × error_count, max 30s)
  5. Wait → attempt reconnection

Keepalive Loop

Monitors connection health with periodic pings:
const KEEP_ALIVE_INTERVAL_MIN: Duration = Duration::from_secs(20);
const KEEP_ALIVE_INTERVAL_MAX: Duration = Duration::from_secs(30);
const KEEP_ALIVE_MAX_FAIL_TIME: Duration = Duration::from_secs(180);
const KEEP_ALIVE_RESPONSE_DEADLINE: Duration = Duration::from_secs(20);
Behavior:
  • Sends ping every 20-30 seconds (randomized)
  • Waits up to 20s for response
  • Forces reconnect after 180s of consecutive failures

Offline Sync

When reconnecting, the client tracks offline message sync progress:
pub(crate) struct OfflineSyncMetrics {
    pub active: AtomicBool,
    pub total_messages: AtomicUsize,
    pub processed_messages: AtomicUsize,
    pub start_time: Mutex<Option<Instant>>,
}
Sync Flow:
  1. Receive <ib><offline_preview count="N"/> → start tracking
  2. Process messages with offline attribute → increment counter
  3. Receive <ib><offline/> → sync complete
  4. Emit OfflineSyncCompleted event

Concurrency Patterns

Per-Chat Message Queues

Prevents race conditions where a later message is processed before the PreKey message:
pub(crate) message_queues: Cache<String, mpsc::Sender<Arc<Node>>>,

Per-Device Session Locks

Prevents concurrent Signal protocol operations on the same session:
pub(crate) session_locks: Cache<String, Arc<tokio::sync::Mutex<()>>>,

Background Saver

Periodic persistence with dirty flag optimization:
impl PersistenceManager {
    pub fn run_background_saver(self: Arc<Self>, interval: Duration) {
        tokio::spawn(async move {
            loop {
                // Wait for notification or interval
                self.save_to_disk().await;
            }
        });
    }
}

Feature Organization

Location: src/features/
features/
├── mod.rs              // Feature exports
├── groups.rs           // Group management
├── presence.rs         // Presence updates
├── media.rs            // Media upload/download
└── contacts.rs         // Contact operations
Pattern:
impl Client {
    /// High-level feature method
    pub async fn create_group(&self, subject: &str, participants: &[Jid]) -> Result<Jid> {
        // Use wacore IqSpec for protocol
        let spec = CreateGroupIq::new(subject, participants)?;
        self.execute(spec).await
    }
}

State Management Flow

Best Practices

State Management

// Use DeviceCommand for state changes
client.persistence_manager
    .process_command(DeviceCommand::SetPushName(name))
    .await;

Async Operations

// Wrap blocking I/O in spawn_blocking
let result = tokio::task::spawn_blocking(move || {
    // Heavy crypto or blocking HTTP
    expensive_operation()
}).await?;

Error Handling

use thiserror::Error;
use anyhow::Result;

#[derive(Debug, Error)]
pub enum SocketError {
    #[error("connection closed")]
    Closed,
    #[error("encryption failed: {0}")]
    Encryption(String),
}

// Use anyhow::Result for functions with multiple error types
pub async fn complex_operation() -> Result<()> {
    // Automatically converts errors with ?
    socket_operation()?;
    storage_operation()?;
    Ok(())
}