> ## Documentation Index
> Fetch the complete documentation index at: https://personal-9eca1d6c-claude-eager-cerf-ucum3s.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# wacore

> Platform-agnostic WhatsApp protocol implementation

## Overview

`wacore` is the core WhatsApp protocol implementation for whatsapp-rust. It's designed to be **platform-agnostic** with no runtime dependencies on Tokio or specific databases, making it portable across different async runtimes and storage backends.

```toml theme={null}
[dependencies]
wacore = "0.6"
```

## Philosophy

wacore contains all the core logic for:

* Binary protocol encoding/decoding
* Cryptographic primitives (AES-GCM, Signal Protocol)
* IQ protocol types and specifications
* Runtime abstraction (`Runtime` trait for pluggable async executors)
* Network abstractions (`Transport`, `TransportFactory`, `HttpClient` traits)
* State management traits (`Backend`, `SignalStore`, `AppSyncStore`, etc.)
* Message builders and parsers

It has **zero runtime dependencies** — no Tokio, no async-std, only `futures`, `async-trait`, `async-lock`, and `async-channel` for async primitives. This makes `wacore` portable to any async runtime, including WASM targets. The main `whatsapp-rust` crate provides concrete implementations (Tokio runtime, SQLite storage, ureq HTTP client, Tokio WebSocket transport).

## Key Exports

### Re-exported Crates

```rust theme={null}
pub use aes_gcm;                      // AES-GCM encryption
pub use wacore_appstate as appstate;  // App state sync
pub use wacore_noise as noise;        // Noise Protocol
pub use wacore_libsignal as libsignal; // Signal Protocol
```

### Derive Macros

```rust theme={null}
pub use wacore_derive::{EmptyNode, ProtocolNode, StringEnum};
```

* **`EmptyNode`** - For protocol nodes with only a tag (no attributes)
* **`ProtocolNode`** - For protocol nodes with string attributes
* **`StringEnum`** - For enums with string representations

See [Binary Protocol](/advanced/binary-protocol) for usage examples.

### Framing

```rust theme={null}
pub use wacore_noise::framing; // WebSocket frame encoding/decoding
```

## Core Modules

### Protocol & Binary

<CardGroup cols={2}>
  <Card title="Binary protocol" icon="code" href="/advanced/binary-protocol">
    Type-safe protocol node builders and parsers
  </Card>

  <Card title="xml" icon="brackets-curly">
    XML utilities for protocol nodes
  </Card>
</CardGroup>

### Cryptography

<CardGroup cols={2}>
  <Card title="Signal Protocol" icon="shield-halved" href="/advanced/signal-protocol">
    Signal Protocol implementation for E2E encryption
  </Card>

  <Card title="noise" icon="fingerprint">
    Noise Protocol XX for handshake encryption
  </Card>
</CardGroup>

### IQ Protocol

```rust theme={null}
pub mod iq;
```

Type-safe IQ request/response specifications:

* **`blocklist`** - Block/unblock contacts
* **`chatstate`** - Typing indicators, presence
* **`contacts`** - Contact synchronization
* **`dirty`** - Dirty bit checking
* **`groups`** - Group management operations
* **`keepalive`** - Connection keepalive
* **`mediaconn`** - Media server connections
* **`mex`** - Message Extension queries
* **`passive`** - Passive IQ handling
* **`prekeys`** - Prekey distribution
* **`privacy`** - Privacy settings
* **`props`** - Server properties and A/B experiment configs. The companion [`abprops`](#a-b-props-registry) module ships the typed flag registry, and `props::WATCHED` lists the flags the library itself reads
* **`spam_report`** - Spam reporting
* **`tctoken`** - Temporary client tokens
* **`usync`** - User synchronization

See [Architecture](/concepts/architecture) for the IQ protocol pattern.

#### A/B props registry

`wacore::iq::abprops` is an auto-generated, vendored snapshot of WhatsApp Web's A/B-props registry. Each WA Web registry becomes a `pub mod` (currently `web`), and each flag becomes a typed `pub const AbProp` named after its key in screaming snake case (for example `web::PRIVACY_TOKEN_SENDING_ON_GROUP_CREATE`). Every entry carries:

* `name` — the wire name the server uses in `<prop config_code="…"/>`
* `code` — the numeric `config_code`
* `value_type` — `AbPropType::Bool`, `Int`, `Float`, or `Str`
* `default` — the registry default applied when the server omits the flag

Pass these constants to [`AbPropsCache`](/api/client#ab-props-cache) (`is_enabled`, `get`, `get_int`, `watch`, `watch_many`) instead of raw `u32` codes. The library only materializes the consts you reference, so the rest of the \~2,000-flag registry adds no binary weight.

```rust theme={null}
use wacore::iq::abprops::web;

let flag = web::ADMIN_REVOKE_RECEIVER;
println!("{} (code {}) defaults to {:?}", flag.name, flag.code, flag.default);
```

`wacore::iq::props::WATCHED` is the slice of flags the library itself reads — useful as a starting point if you want to extend the cache's interest set. A small `props::stale` module preserves flags the client still references but that the current WA Web bundle no longer ships.

### Message Handling

<CardGroup cols={2}>
  <Card title="messages" icon="message">
    Message encryption and decryption
  </Card>

  <Card title="send" icon="paper-plane">
    Message sending logic
  </Card>

  <Card title="download" icon="download">
    Media download and decryption
  </Card>

  <Card title="upload" icon="upload">
    Media encryption and upload preparation
  </Card>
</CardGroup>

### State Management

```rust theme={null}
pub mod store;
```

* **`Device`** - Core device state structure
* **`DeviceCommand`** - State mutation commands
* **`traits`** - Backend trait definitions (`Backend`, `SessionStore`, etc.)
* **`ab_props`** - In-memory A/B experiment property cache (`AbPropsCache`), populated from `fetch_props()` on each connection. Features query this cache using typed flag constants from the [`abprops` registry](#a-b-props-registry) (e.g., privacy token attachment on group operations).

See [State Management](/advanced/state-management) for the command pattern.

### Runtime & Networking

```rust theme={null}
pub mod runtime;  // Runtime trait, AbortHandle, timeout(), blocking()
pub mod net;      // Transport, TransportFactory, HttpClient, TransportEvent
```

The `runtime` module defines the `Runtime` trait that all async operations go through:

```rust theme={null}
pub trait Runtime: Send + Sync + 'static {
    fn spawn(&self, future: Pin<Box<dyn Future<Output = ()> + Send + 'static>>) -> AbortHandle;
    fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send>>;
    fn spawn_blocking(&self, f: Box<dyn FnOnce() + Send + 'static>) -> Pin<Box<dyn Future<Output = ()> + Send>>;
    fn yield_now(&self) -> Option<Pin<Box<dyn Future<Output = ()> + Send>>>;

    /// How often to yield in tight loops (every N items). Defaults to 10.
    fn yield_frequency(&self) -> u32 { 10 }
}
```

| Method            | Purpose                                                                                                                                                                     |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `spawn`           | Spawn a background task, returning an `AbortHandle` for cancellation. The handle is `#[must_use]` — dropping it aborts the task. Call `.detach()` for fire-and-forget tasks |
| `sleep`           | Return a future that completes after a duration                                                                                                                             |
| `spawn_blocking`  | Offload a blocking closure to a thread pool                                                                                                                                 |
| `yield_now`       | Cooperatively yield; return `None` if unnecessary (e.g., multi-threaded runtimes)                                                                                           |
| `yield_frequency` | How many items to process before yielding in tight loops (default: `10`)                                                                                                    |

#### Helper functions

The `runtime` module also provides two runtime-agnostic helper functions that work with any `Runtime` implementation:

**`timeout`** — Race a future against a deadline using the runtime's `sleep` implementation:

```rust theme={null}
pub async fn timeout<F, T>(
    rt: &dyn Runtime,
    duration: Duration,
    future: F,
) -> Result<T, Elapsed>
where
    F: Future<Output = T>,
```

Returns `Ok(value)` if the future completes before the duration, or `Err(Elapsed)` if it times out. This is the runtime-agnostic replacement for `tokio::time::timeout` — the library uses it internally for phash validation, media retry timeouts, session establishment, and IQ response waiting.

**`blocking`** — Offload a blocking closure and return its result:

```rust theme={null}
pub async fn blocking<T: Send + 'static>(
    rt: &dyn Runtime,
    f: impl FnOnce() -> T + Send + 'static,
) -> T
```

Wraps `Runtime::spawn_blocking` with a oneshot channel to ferry the closure's return value back to the caller. On WASM, the closure runs inline since there is only one thread.

The `net` module defines the networking abstractions:

* **`Transport`** — active connection for sending/receiving raw bytes
* **`TransportFactory`** — creates new transport instances and event streams
* **`HttpClient`** — HTTP request execution (buffered and streaming)
* **`TransportEvent`** — `Connected`, `DataReceived(Bytes)`, `Disconnected`

On WASM targets (`target_arch = "wasm32"`), all `Send` bounds are automatically removed.

### Connection & Pairing

<CardGroup cols={2}>
  <Card title="handshake" icon="handshake">
    Noise Protocol handshake
  </Card>

  <Card title="pair" icon="qrcode">
    QR code pairing
  </Card>

  <Card title="pair_code" icon="mobile">
    Phone number pairing
  </Card>

  <Card title="net" icon="network-wired">
    Transport and HTTP client traits
  </Card>
</CardGroup>

### Specialized Features

<CardGroup cols={2}>
  <Card title="appstate" icon="database">
    App state synchronization (contacts, settings)
  </Card>

  <Card title="history_sync" icon="clock-rotate-left">
    Message history synchronization
  </Card>

  <Card title="usync" icon="users">
    User device list synchronization
  </Card>

  <Card title="prekeys" icon="key">
    Signal Protocol prekey generation
  </Card>
</CardGroup>

### Time

```rust theme={null}
pub mod time;
```

The `time` module centralizes all timestamp handling. It exposes two independent clocks that should not be confused:

* **Wall clock** (`TimeProvider`, `now_millis`, `now_utc`) — answers "what time is it?". May jump backwards across NTP syncs, manual adjustments, or leap-second smearing. Backed by `chrono::Utc::now()` on native targets.
* **Monotonic clock** (`MonotonicProvider`, `Instant`) — answers "how much time passed?". Never moves backwards and is immune to NTP adjustments. Backed by `std::time::Instant` on native targets.

<Warning>
  On `wasm32-unknown-unknown` there is no built-in wall-clock source. If you do not register a provider before the first timestamp, `now_millis()` returns `0` (Unix epoch) and logs a single warning. Always call `set_time_provider` with a `Date.now()`-backed (or equivalent) provider during WASM startup — see [Custom wall-clock provider](#custom-wall-clock-provider) below.
</Warning>

Use the wall clock for stanza timestamps, app-state mutations, and log lines. Use the monotonic clock for timeouts, retry backoff, and latency measurements. Conflating the two silently corrupts elapsed-time logic whenever the system clock is adjusted mid-measurement.

Each clock can be overridden globally — useful in environments where the standard implementations are unavailable (e.g., WASM) or for deterministic testing.

#### Wall-clock functions

| Function                 | Return type             | Description                                                             |
| ------------------------ | ----------------------- | ----------------------------------------------------------------------- |
| `now_millis()`           | `i64`                   | Current time in milliseconds since Unix epoch                           |
| `now_secs()`             | `i64`                   | Current time in seconds since Unix epoch                                |
| `now_utc()`              | `DateTime<Utc>`         | Current time as a `chrono::DateTime<Utc>`                               |
| `from_secs(ts)`          | `Option<DateTime<Utc>>` | Convert Unix timestamp (seconds) to `DateTime<Utc>`                     |
| `from_secs_or_now(ts)`   | `DateTime<Utc>`         | Like `from_secs`, falling back to `now_utc()` for out-of-range values   |
| `from_millis(ts)`        | `Option<DateTime<Utc>>` | Convert Unix timestamp (milliseconds) to `DateTime<Utc>`                |
| `from_millis_or_now(ts)` | `DateTime<Utc>`         | Like `from_millis`, falling back to `now_utc()` for out-of-range values |

#### Custom wall-clock provider

Implement the `TimeProvider` trait and call `set_time_provider` before any time functions are used:

```rust theme={null}
use wacore::time::{TimeProvider, set_time_provider};

struct FixedTime;

impl TimeProvider for FixedTime {
    fn now_millis(&self) -> i64 {
        1700000000000 // Fixed timestamp for testing
    }
}

// Must be called before any time functions are used
set_time_provider(FixedTime).expect("provider already set");
```

<Note>
  `set_time_provider` returns `Err` if a provider has already been set. The provider uses `OnceLock` internally, so it can only be configured once per process.
</Note>

On `wasm32` targets the epoch fallback is not cached, so a later `set_time_provider` call still takes effect even if a timestamp was read during startup. A typical browser embedder wires `Date.now()` through `wasm-bindgen`:

```rust theme={null}
use wacore::time::{TimeProvider, set_time_provider};

struct JsDateProvider;

impl TimeProvider for JsDateProvider {
    fn now_millis(&self) -> i64 {
        js_sys::Date::now() as i64
    }
}

// Register before constructing the client or sending stanzas.
set_time_provider(JsDateProvider).expect("provider already set");
```

#### Instant

`Instant` is a portable monotonic instant that replaces `std::time::Instant`, which is unavailable on `wasm32-unknown-unknown`. On native targets it wraps `std::time::Instant` via the default `MonotonicProvider` and exposes nanosecond resolution. The `Instant` type is `Copy` and supports `Add<Duration>` and `Sub<Instant>` (returning `Duration`), with saturating arithmetic to prevent overflow.

```rust theme={null}
use wacore::time::Instant;

let start = Instant::now();
// ... do work ...
let elapsed = start.elapsed(); // std::time::Duration
```

#### Custom monotonic provider

Implement the `MonotonicProvider` trait and call `set_monotonic_provider` before any `Instant` is captured. The provider must return nanoseconds since an arbitrary fixed reference and never return a smaller value than a previous call.

```rust theme={null}
use wacore::time::{MonotonicProvider, set_monotonic_provider};

struct PerfNowProvider;

impl MonotonicProvider for PerfNowProvider {
    fn now_nanos(&self) -> u64 {
        // e.g., wrap performance.now() in browsers, or hrtime in Node
        (js_sys::performance().now() * 1_000_000.0) as u64
    }
}

set_monotonic_provider(PerfNowProvider).expect("provider already set");
```

On `wasm32` targets without a registered provider, the fallback derives nanos from the wall clock (clamped to non-decreasing) and quantizes to milliseconds. Embedders targeting browsers, Node, or WASI should register a sub-millisecond provider for accurate latency measurements.

<Note>
  `set_monotonic_provider` returns `Err` if a provider has already been set. Like the wall-clock provider, it uses `OnceLock` internally and can only be configured once per process.
</Note>

### Utilities

* **`client`** - Client context traits
* **`ib`** - Identity byte utilities
* **`proto_helpers`** - Protobuf conversion helpers
* **`reporting_token`** - Reporting token generation
* **`request`** - Request building utilities
* **`stanza`** - Common stanza builders
* **`sticker_pack`** - Sticker pack creation helpers (see [sticker packs](/guides/sending-messages#sticker-packs))
* **`time`** - Pluggable wall-clock and monotonic-clock providers, plus portable `Instant` (see [time](#time) above)
* **`types`** - Common type definitions (JID, events, messages). Includes `types::jid` utilities for zero-allocation JID comparison (`cmp_for_lock_order`), buffer-reusing address formatting (`write_protocol_address_to`), and in-place sorted deduplication (`sort_dedup_by_user`, `sort_dedup_by_device`)
* **`version`** - WhatsApp version constants
* **`webp`** - WebP format utilities (animated sticker detection)

### webp module

The `webp` module provides utilities for working with WebP image files. It is re-exported as `whatsapp_rust::webp`.

#### is\_animated

Detects whether a WebP file contains animation frames by parsing RIFF/VP8X headers and scanning for ANIM/ANMF chunks.

```rust theme={null}
pub fn is_animated(data: &[u8]) -> bool
```

<ParamField path="data" type="&[u8]" required>
  Raw WebP file bytes.
</ParamField>

Returns `true` if the WebP file is animated, `false` otherwise (including for invalid or too-short input).

**Example:**

```rust theme={null}
use whatsapp_rust::webp;

let webp_bytes = std::fs::read("sticker.webp")?;

if webp::is_animated(&webp_bytes) {
    println!("Animated sticker");
} else {
    println!("Static sticker");
}
```

<Note>
  This function is used internally by `create_sticker_pack_zip` to set the `is_animated` field on each sticker proto entry. You can also use it directly when you need to classify WebP files before processing.
</Note>

## Submodule Packages

wacore is split into several workspace crates:

### wacore-binary

**Location:** `wacore/binary`

Binary protocol encoding/decoding using WhatsApp's custom format.

```rust theme={null}
use wacore_binary::{
    CompactString,
    jid::Jid,
    node::Node,
    builder::NodeBuilder,
    marshal::{marshal, unmarshal_ref},
};

let node = NodeBuilder::new("message")
    .attr("type", "text")
    .attr("to", "15551234567@s.whatsapp.net")
    .build();

let bytes = marshal(&node)?;
let decoded = unmarshal_ref(&bytes)?;
```

**Key exports:**

* `CompactString` - Re-export of `compact_str::CompactString`, used by `Jid.user`, `NodeValue::String`, and `NodeContent::String`
* `jid::{Jid, JidRef, Server, JidExt}` - WhatsApp JID types. `Server` is an enum (`#[repr(u8)]`) with variants for all known WhatsApp server domains (`Pn`, `Lid`, `Group`, `Broadcast`, `Newsletter`, `Hosted`, `HostedLid`, `Messenger`, `Interop`, `Bot`, `Legacy`). `JidExt` provides helper methods (`is_group()`, `is_newsletter()`, etc.) for both owned and borrowed JID types
* `node::{Node, NodeRef, NodeStr, NodeValue, ValueRef, OwnedNodeRef}` - Protocol node types. `Node` (owned) for building outgoing stanzas, `NodeRef` (borrowed) for reading received stanzas, `NodeStr` for borrowed-or-inline decoded strings, `OwnedNodeRef` for yoke-based zero-copy self-referential nodes shared as `Arc<OwnedNodeRef>`. The entire `NodeRef` type family (`NodeRef`, `NodeStr`, `ValueRef`, `JidRef`, `NodeContentRef`, `OwnedNodeRef`) implements `serde::Serialize`, producing output identical to their owned counterparts — enabling zero-copy serialization without converting to `Node` first
* `builder::NodeBuilder` - Fluent node builder with `new(&'static str)` / `new_dynamic(String)`, `attr()`, `jid_attr()`, `children()`, `bytes()`, `string_content()`, and `apply_content()` chaining methods
* `marshal::*` - Binary marshaling functions
* `attrs::{AttrParser, AttrParserRef}` - Attribute parsing utilities for owned `Node` and borrowed `NodeRef` respectively
* `token` - Token dictionary

### wacore-libsignal

**Location:** `wacore/libsignal`

Signal Protocol implementation for end-to-end encryption.

```rust theme={null}
use wacore::libsignal::{
    core::SessionCipher,
    protocol::{PreKeyBundle, PublicKey},
    store::SessionStore,
};
```

**Key modules:**

* `core` - Core session cipher logic
* `crypto` - Cryptographic primitives (HKDF, HMAC, AES)
* `protocol` - Protocol message types
* `store` - Store trait definitions

See [Signal Protocol](/advanced/signal-protocol) for encryption details.

### wacore-noise

**Location:** `wacore/noise`

Noise Protocol implementation for handshake encryption. Supports the XX, IK, and XXfallback patterns to match WhatsApp Web's behavior on cold start, resumed reconnects, and server-driven recovery.

```rust theme={null}
use wacore::noise::{
    NoiseHandshake,
    HandshakeUtils,
    XxHandshakeState,
    IkHandshakeState,
    XxFallbackHandshakeState,
    IkServerHelloOutcome,
    VerifiedServerCertChain,
    build_handshake_header,
};
```

**Key exports:**

* `NoiseState` - Generic Noise state machine
* `NoiseHandshake` - WhatsApp-specific handshake wrapper
* `XxHandshakeState` - Three-message XX handshake (cold start / fallback)
* `IkHandshakeState` - Resumed IK handshake using a cached server static
* `XxFallbackHandshakeState` - Server-driven recovery from a stale IK static, continuing the existing transcript
* `IkServerHelloOutcome` - Either `Continue` (IK succeeds) or `Fallback` (pivot into XXfallback)
* `VerifiedServerCertChain` - Output of XX/XXfallback; persisted by the client to enable IK on the next connect
* `HandshakeUtils` - Protocol message building/parsing
* `framing` - WebSocket frame encoding
* `build_edge_routing_preintro` - Edge routing helper

See [WebSocket & Noise Protocol — Noise Protocol Handshake](/advanced/websocket-handling#noise-protocol-handshake) for the full pattern selection and failure-handling logic.

### wacore-appstate

**Location:** `wacore/appstate`

App state synchronization for contacts, settings, and metadata.

```rust theme={null}
use wacore::appstate::{
    process_snapshot,
    process_patch,
    expand_app_state_keys,
    Mutation,
};
```

**Key exports:**

* `process_snapshot` - Process full state snapshots
* `process_patch` - Apply incremental patches
* `Mutation` - State mutation records
* `LTHash` - LTHash implementation for integrity
* `expand_app_state_keys` - Key derivation

### wacore-derive

**Location:** `wacore/derive`

Procedural macros for protocol node generation.

```rust theme={null}
use wacore::{EmptyNode, ProtocolNode, StringEnum};

#[derive(EmptyNode)]
#[protocol(tag = "participants")]
pub struct ParticipantsRequest;

#[derive(ProtocolNode)]
#[protocol(tag = "query")]
pub struct QueryRequest {
    #[attr(name = "request", default = "interactive")]
    pub request_type: String,
}

#[derive(StringEnum)]
pub enum Action {
    #[str = "block"]
    Block,
    #[str = "unblock"]
    Unblock,
}
```

## Usage in main library

The main `whatsapp-rust` crate uses wacore modules throughout:

```rust theme={null}
// Binary protocol
use wacore_binary::jid::Jid;
use wacore_binary::builder::NodeBuilder;

// Signal Protocol
use wacore::libsignal::store::SessionStore;
use wacore::libsignal::protocol::PreKeyBundle;

// App state
use wacore::appstate::{
    process_snapshot,
    Mutation,
};

// IQ specs
use wacore::iq::privacy as privacy_settings;

// Store traits
use wacore::store::traits::Backend;

// Protobuf helpers
use wacore::proto_helpers;
```

## Design Principles

### Platform-Agnostic

No dependencies on:

* Tokio or any async runtime — uses only `futures`, `async-trait`, `async-lock`, `async-channel`
* Specific database implementations
* File system operations

This allows users to provide their own:

* **Runtime:** Tokio (default), async-std, smol, WASM, etc. — implement `Runtime` (4 methods)
* **Storage:** SQLite (default), PostgreSQL, in-memory, etc. — implement `Backend` (4 sub-traits)
* **Transport:** Tokio WebSocket (default), custom protocols — implement `TransportFactory` + `Transport`
* **HTTP client:** ureq (default), reqwest, surf, etc. — implement `HttpClient`

### Type Safety

Strong typing throughout:

* `Jid` with `Server` enum for WhatsApp identifiers — server type is an enum variant, not a string
* `Node` / `NodeRef` for protocol messages (owned / borrowed)
* Validated newtypes (e.g., `GroupSubject` with length limits)
* Enum variants with `StringEnum` for protocol values

### Zero-copy where possible

* `Cow<'static, str>` for owned `Node.tag` and `Attrs` keys — known protocol strings (from the token dictionary) are borrowed as static references with zero heap allocation, while unknown strings fall back to owned `String`
* `NodeStr<'a>` for borrowed `NodeRef.tag`, `AttrsRef` keys, `ValueRef::String`, and `JidRef.user` — a borrowed-or-inline string type where the `Owned` variant uses `CompactString` (inline up to 24 bytes) instead of heap-allocated `String`, reducing allocation pressure during decoding
* `Server` enum (`#[repr(u8)]`) for `Jid.server` — a `Copy` type that requires zero allocation, replacing the previous `Cow<'static, str>` string-based server field
* `OwnedNodeRef` — yoke-based self-referential node that owns the decompressed network buffer while `NodeRef` borrows string/byte payloads directly from it. Received stanzas flow through the system as `Arc<OwnedNodeRef>` for cheap shared zero-copy access
* **Zero-copy `Serialize`** — the entire `NodeRef` type family (`NodeRef`, `NodeStr`, `ValueRef`, `JidRef`, `NodeContentRef`, `OwnedNodeRef`) implements `serde::Serialize`, producing output identical to their owned counterparts. This allows serializing received stanzas directly from the network buffer without converting to owned `Node` types first. See [Binary Protocol — Zero-copy serialization](/advanced/binary-protocol#zero-copy-serialization) for details
* `NodeRef` for borrowed node parsing
* `AttrParserRef` for attribute iteration
* `marshal_ref` for encoding without cloning

## Benchmarks

The project includes two categories of benchmarks: **protocol-level** (iai-callgrind, deterministic instruction counts) and **integration-level** (real client operations with allocation tracking).

### Protocol benchmarks (iai-callgrind)

wacore includes a suite of [iai-callgrind](https://github.com/iai-callgrind/iai-callgrind) benchmarks that measure instruction counts for core protocol operations. These benchmarks run under Valgrind's Callgrind tool, producing deterministic, low-noise measurements that are suitable for CI regression tracking.

#### Prerequisites

Running the protocol benchmarks requires:

* **Nightly Rust** (the project pins `nightly-2026-04-05`)
* **Valgrind** installed on your system
* **iai-callgrind-runner** (`cargo install iai-callgrind-runner --version 0.16.1`)

#### Available suites

| Suite                       | Location                                          | What it measures                                                                                                                                                                                                                                                    |
| --------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `send_receive_benchmark`    | `wacore/benches/send_receive_benchmark.rs`        | Full send/receive pipeline — DM send, DM receive, group send (steady-state skmsg), group send with SKDM distribution (10/50/256 participants), and group receive. Uses real `prepare_peer_stanza` and `prepare_group_stanza` functions with in-memory Signal stores |
| `reporting_token_benchmark` | `wacore/benches/reporting_token_benchmark.rs`     | Reporting token generation — key derivation, token calculation, and full generation pipeline for simple and extended messages                                                                                                                                       |
| `binary_benchmark`          | `wacore/binary/benches/binary_benchmark.rs`       | Binary protocol encoding/decoding — marshal and unmarshal operations for various node sizes and structures                                                                                                                                                          |
| `libsignal_benchmark`       | `wacore/libsignal/benches/libsignal_benchmark.rs` | Signal Protocol operations — session establishment, message encrypt/decrypt, sender key distribution, and group cipher operations                                                                                                                                   |

#### Running protocol benchmarks

```bash theme={null}
# Run all benchmarks across the workspace
cargo bench --workspace

# Run a specific benchmark suite
cargo bench -p wacore --bench send_receive_benchmark
cargo bench -p wacore --bench reporting_token_benchmark
cargo bench -p wacore-binary --bench binary_benchmark
cargo bench -p wacore-libsignal --bench libsignal_benchmark
```

Each benchmark produces instruction counts and optionally generates flamegraph SVGs for profiling hotspots.

### Integration benchmarks

The `bench-integration` test suite (`tests/bench-integration/`) measures real-world client operations end-to-end, including wall-clock time and heap allocation counts. It uses a custom `CountingAlloc` global allocator that tracks every allocation and byte count, then produces `customSmallerIsBetter` JSON output for CI trend tracking.

#### Scenarios

| Scenario                   | What it measures                                                                                                                                            |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connect_to_ready`         | Client creation through `Connected` event — includes transport connect, Noise handshake, and initial sync                                                   |
| `send_message`             | Single DM send (sender side) — protobuf encoding, Signal encrypt, node marshal, and WebSocket write. Also measures amortized cost over 20 consecutive sends |
| `send_and_receive_message` | Full round-trip — send a DM and wait for delivery on the receiver. Also measures amortized cost over 20 round-trips                                         |
| `reconnect`                | Disconnect and reconnect cycle — measures the time and allocations to re-establish a session                                                                |

Each scenario reports three metrics: `alloc_count` (number of heap allocations), `alloc_bytes` (total bytes allocated), and `wall_ms` (elapsed wall-clock time). Amortized scenarios divide totals by the number of iterations for per-operation costs.

#### Running integration benchmarks

Integration benchmarks require a mock WhatsApp server (e.g., [Bartender](https://github.com/whiskeysockets-devtools/bartender)):

```bash theme={null}
# Start the mock server (Docker)
docker run -d -p 8080:8080 ghcr.io/whiskeysockets-devtools/bartender:latest

# Run the benchmarks
MOCK_SERVER_URL="wss://127.0.0.1:8080/ws/chat" \
  cargo run -p bench-integration --release
```

The output is a JSON array of `customSmallerIsBetter` entries printed to stdout, suitable for consumption by [github-action-benchmark](https://github.com/benchmark-action/github-action-benchmark).

<Note>
  Integration benchmarks require the `danger-skip-tls-verify` and `debug-diagnostics` features, which are enabled automatically via the `bench-integration` crate's `Cargo.toml`. You can also enable DHAT heap profiling with `--features dhat-heap` for detailed allocation flamegraphs.
</Note>

### Allocation optimizations

The library includes several allocation-reduction strategies that the integration benchmarks track:

* **Thread-local zlib pool** — The binary protocol decompressor (`decompress_zlib_pooled`) reuses a thread-local `flate2::Decompress` instance (\~48 KB internal state) and output buffer across calls, avoiding per-frame heap allocations
* **`CompactString` for JIDs** — JID user fields use `compact_str::CompactString` which stores strings up to 24 bytes inline (no heap allocation), covering the vast majority of phone numbers and LID identifiers
* **`Server` enum** — JID server fields use a `#[repr(u8)]` enum instead of heap-allocated strings, making JID construction and comparison zero-allocation
* **Zero-copy node decoding** — Received stanzas are decoded as `NodeRef` borrowing directly from the network buffer via `OwnedNodeRef` (yoke-based self-referential type), avoiding cloning string/byte payloads during decode
* **Pre-allocated buffers** — History sync decompression uses a `compressed_size_hint` with a 4x multiplier for buffer pre-allocation, reducing `Vec` reallocation during decompression

### CI integration

The repository includes GitHub Actions workflows for both benchmark types:

**Protocol benchmarks** (`.github/workflows/benchmark.yml`):

* Runs on every push to `main` and on pull requests
* Uses [github-action-benchmark](https://github.com/benchmark-action/github-action-benchmark) to store baselines and generate trend charts on GitHub Pages
* A custom parser script (`.github/scripts/iai-to-benchmark-json.py`) converts iai-callgrind instruction counts into the `customSmallerIsBetter` JSON format
* Pull requests receive a collapsed PR comment (via `.github/scripts/bench-comment.py`) grouping benchmarks into **regressions** (>5% slower), **improvements** (>5% faster), and **unchanged**
* The PR comment is idempotent — subsequent benchmark runs update the existing comment instead of creating duplicates

**Integration benchmarks** (`.github/workflows/bench-integration.yml`):

* Runs on every push to `main` and on pull requests
* Spins up a Bartender mock server as a Docker service container
* Measures allocation counts, allocation bytes, and wall-clock time for each scenario
* Pushes to `main` store the baseline for trend tracking
* Pull requests compare against the baseline

Raw benchmark output for both suites is uploaded as artifacts (retained for 30 days) for debugging.

## Next steps

<CardGroup cols={2}>
  <Card title="waproto" icon="file-code" href="/api/waproto">
    Protocol Buffers message definitions
  </Card>

  <Card title="Binary Protocol" icon="diagram-project" href="/advanced/binary-protocol">
    Type-safe protocol node pattern
  </Card>

  <Card title="Architecture" icon="message-code" href="/concepts/architecture">
    IqSpec request/response pairing
  </Card>

  <Card title="State Management" icon="database" href="/advanced/state-management">
    Device state and commands
  </Card>
</CardGroup>
