Skip to content

v0.6.0

Latest

Choose a tag to compare

@github-actions github-actions released this 11 May 14:39
· 272 commits to main since this release
56ed1b0

This is the largest release since the project's first crates.io publish. It brings the receive and send pipelines into WhatsApp Web behavioural parity, lands a major performance overhaul (zero-copy node decoding, take-ownership session cache, allocation diet across every hot path), adds first-class support for albums, sticker packs, ephemeral chats, status reactions, message-edit decryption and group call detection, and exposes new extension points for HTTP, transport, and crypto.

Many APIs changed — see Breaking changes below — but the new shape eliminates a long tail of clones, redundant decodes, and stringly-typed parameters that were holding back further optimisation. Downstream crates will need a one-shot migration; in exchange the receive pipeline performs effectively zero deep clones on dispatch.

Highlights

  • Message edits via secret_encrypted_message (#618) — WhatsApp's new E2E envelope for edits (secretEncType = MESSAGE_EDIT) is now decryptable. Ships MessageEdits::decrypt and a generalised secret_enc_addon helper covering the full addon family (PollVote, PollEdit, PollAddOption, EventResponse, EventEdit, EncReaction, EncComment, ReportToken, MessageEdit).
  • Album sends (#449) and sticker pack sends (#454).
  • Ephemeral messages (#450) — send and receive of disappearing chats.
  • Status reactions (#541, #563, #568, #569).
  • Pin / unpin (#424), Client::logout() (#422), per-message message_id in SendOptions (#423).
  • Incoming call events + group call detection (#562, #599).
  • ClientProfile for noise-handshake identity (#596) and Noise_IK + XXfallback (#598).
  • Pluggable SignalCryptoProvider (#561) — bring your own AES-CBC/GCM + HMAC-SHA256 backend.
  • Proxy + custom TLS extension points (#536) and a generic from_websocket transport API (#486).
  • Public refresh_prekeys() (#538) for device-migration flows.
  • LID + PN support in is_on_whatsapp (#453).
  • Lazy history sync blob (#533) — history sync events expose a LazyHistorySync you decode on demand.
  • Full GroupMetadata returned by create_group / community.create (#615).
  • MEX notification dispatcher + consolidated doc-id registry (#574).
  • 32-bit target support via portable-atomic (#481).

Performance

This release is built around a sustained allocation-discipline pass on the receive, send, and signal paths. Selected wins:

  • Receive pipeline: zero-copy node decoding via yoke (#513), NodeStr replacing Cow<str> for inline decoded strings (#514), Arc<wa::Message> end-to-end on dispatch (#613), and Arc<MessageInfo> across retry/PDO (#520). Zero-copy receive pipeline + take-ownership session cache (#522).
  • Startup: −21% allocations, −31% peak heap (#525).
  • Connect time: −23% total allocs, −43% connect-time allocs (#551).
  • Hot-path audits: 11 trims from a targeted pass (#570), per-device sender-key tracking cache (#445), parallel group encrypt fan-out (#610).
  • Token lookup: PHF + SipHash replaced with hashify PTHash (FNV-1a) (#544); single/double-byte token maps unified into one PHF lookup (#543).
  • Jid / nodes: CompactString for Jid.user, NodeValue, NodeContent (#512); zero-copy Serialize for the NodeRef family (#539); zero-alloc dedup (#500); eliminated fmt::Write dispatch from JID formatting (#540).
  • Cache miss penalty: unified device resolution removed a ~700 ms latency spike on cold lookups (#428).
  • Signal store: lightweight PN, cache-first identity, reusable flush buffer (#554); Bytes for sessions, [u8; 32] for identities, pre-encoded acks (#555).
  • Decrypt hot path: LID-PN persist + migrations off-loaded from the decrypt path (#578).

WhatsApp Web compliance

A major theme of 0.6.0. The wire format, addressing, retry, and key-management code paths now match WA Web's reference behaviour:

  • Pair + ADVcompanion_platform_id derivation, ADV HMAC enforcement, <prop> round-trip, Android → Chrome mapping (#592, #593, #594, #595, #601).
  • DM multi-device fanout with phash validation (#524); single-device DM retry with chat/requester separation + alternate key lookup (#549, #550).
  • Group / status session rebuild on retry (#559).
  • SKDM — always distribute on first group send, keep tracker on identity change, close forward-secrecy gaps (#603, #604).
  • LID / PN / Hosted addressing — Signal address for Hosted JIDs (#605), unified get_lid_pn_entry(&Jid) (#487), session migration via signal cache (#482), status@broadcast resolves to @lid (#609).
  • Identity change handler completed to match WA Web (#489, #490).
  • PDO recovery + UndecryptableMessage dispatch (#585), <unavailable> via PDO (#506).
  • Stale device filtering via decoded key-index-list (#469); sender-key cache invalidation on device changes (#489).
  • view_once nested-wrapper detection (#602).
  • Block IQ includes LID + pn_jid (#600).
  • Proto bumped to WhatsApp Web 2.3000.1035617621 (#436).

Breaking changes

API ergonomics

  • &[Jid] slices replace Vec<Jid> across feature APIs (#457).
  • #[non_exhaustive] added to event and option enums (#457) — new variants no longer break downstream matches.
  • Typed errors preserve sources across the workspace (#597) — anyhow::Error returns replaced by thiserror enums with #[source], so callers can downcast.
  • Stringly-typed parameters → enums for privacy settings (#438) and several other APIs (#440).
  • Enum JSON discriminators aligned with wire tags via StringEnum (#566); wire-tag derives unified under #[derive(WireEnum)] (#567).

Sender key & device registry

  • Unified per-device sender-key tracking through the device registry (#444).
  • key-index-list decoded to filter stale devices (#469).
  • server_has_prekeys flag persisted to match WA Web (#492).
  • Immutable load_sender_key and auto-reserve marshal (#503).

Device / pair

  • DevicePropsOverride builder; DEVICE_PROPS aligned with WA Web (#586).
  • companion_platform_{id,display} now derived from DeviceProps (#592, #593).
  • PlatformType::Android maps to Chrome; PlatformType::Unknown removed (#601).

Newsletter

  • Sends unified through Client::send_message (#447); newsletter meta node included on every send (#448).

Event bus

  • Arc<Event> event bus eliminates deep clones on dispatch (#515).
  • Arc<wa::Message> end-to-end (#613); Arc<MessageInfo> across message/retry/PDO (#520).

History sync

  • Events replaced with LazyHistorySync (#533) — decode on demand instead of eagerly.

Other

  • LID + PN supported in is_on_whatsapp (#453).
  • Sticker pack APIs (#454).
  • Allocation refactor in upload/send/Jid paths (#471).
  • Comprehensive group feature improvements (#527).
  • Unified LID-PN lookup via get_lid_pn_entry(&Jid) (#487).
  • Stale PN sessions migrate to LID to fix NoSession decryption failures (#475).
  • CompactString for NodeValue / NodeContent / Jid.user (#512); yoke zero-copy node decoding + Jid Server enum (#513); NodeStr replaces Cow<str> (#514).
  • message_enqueue_locks + message_queues merged into ChatLane (#516).

Reliability

  • Graceful shutdown for detached tasks fixes WASM setTimeout leaks (#560).
  • Concurrent disconnect hang resolved via flush_scope counter and socket teardown (#577).
  • In-flight delivery receipts flushed on disconnect (#573).
  • Read-loop blocking + keepalive blind spots fixed (#485).
  • Silent pkmsg drop during offline/online transition fixed (#443).
  • Audit follow-ups across correctness/safety/perf (#460, #511, #584).
  • <unavailable> alongside <enc> no longer swallowed (#589).
  • LID↔PN zombie path closed for group-prekey 406 latency spikes (#579).
  • Centralised timestamps via wacore::time with clippy enforcement (#588, #611).

Contributors

Thanks to everyone who shipped code into this release:

  • @jlucaso1 — the lion's share of receive/send/signal/perf work, group features, pairing, history sync, MEX dispatcher, and the WA Web compliance pass.
  • @Salientekill — timestamp centralisation, typed membership-request variants, group call detection, blocklist LID/pn_jid fix, Arc<wa::Message> sharing.
  • @mcaldas<unavailable> handling via PDO instead of silent drops.
  • @aldoeliacim — public refresh_prekeys() for device migration.
  • @Copilot — targeted single-device DM retry refactor matching WA Web.
  • @dependabot — kept dependencies current across the release window (aes, rand, rustls, compact_str, yoke, libsqlite3-sys).

New contributors


Full changelog: v0.5.0...v0.6.0 — over 200 PRs across the receive/send/signal pipelines, group features, pairing, history sync, performance, and WA Web parity.

All PRs in this release (auto-generated, full attribution)
  • perf: optimize e2e CI — release build + skip soak tests by @jlucaso1 in #417
  • fix: media stanza type, enc mediatype, thumbnails, and quote remoteJid by @jlucaso1 in #418
  • refactor: forward media via CDN reuse, simplify main.rs by @jlucaso1 in #419
  • perf: remove upload buffer clone, DRY download params and error patterns by @jlucaso1 in #420
  • perf: hot-path allocations, ProductCatalogImage fix, DRY and dead code cleanup by @jlucaso1 in #421
  • feat: add Client::logout() by @jlucaso1 in #422
  • feat: add message_id to SendOptions by @jlucaso1 in #423
  • feat: add pin_message() / unpin_message() by @jlucaso1 in #424
  • fix: auto-detect stanza metadata for pin/poll/event messages by @jlucaso1 in #425
  • fix: content-based decrypt-fail, poll vote/event response meta, SKDM decrypt-fail by @jlucaso1 in #426
  • feat: auto-detect biz node, fix stanza types, serde-snake-case by @jlucaso1 in #427
  • perf!: unify device resolution through device_registry, eliminate ~700ms cache miss penalty by @jlucaso1 in #428
  • fix: use BadMac (error 7) for MAC failures, treat empty sessions as missing by @jlucaso1 in #429
  • refactor: replace DeviceRow positional tuple with named struct by @jlucaso1 in #434
  • chore: update proto to WhatsApp Web 2.3000.1035617621 by @jlucaso1 in #436
  • Implement end-to-end tests for TC token by @jlucaso1 in #437
  • refactor: make privacy settings API type-safe and WA Web compliant by @jlucaso1 in #438
  • fix: correct stanza type and meta node mappings to match WA Web by @jlucaso1 in #439
  • feat: cstoken (NCT) privacy token fallback + proto update by @jlucaso1 in #433
  • refactor: replace stringly-typed APIs with enums across codebase by @jlucaso1 in #440
  • fix: parse digest prekey IDs from nodes, not by @jlucaso1 in #441
  • feat: add AB props cache and privacy tokens conditional by @jlucaso1 in #442
  • fix: prevent silent pkmsg drop during offline-online semaphore transition by @jlucaso1 in #443
  • feat!: unified per-device sender key tracking (WA Web parity) by @jlucaso1 in #444
  • perf: in-memory cache for sender key device tracking by @jlucaso1 in #445
  • fix: audit findings — prekey wrap-around, chain key overflow, log levels by @jlucaso1 in #446
  • refactor!: unify newsletter send through Client::send_message by @jlucaso1 in #447
  • fix: include meta node in newsletter sends (WA Web parity) by @jlucaso1 in #448
  • feat!: album message support by @jlucaso1 in #449
  • feat: ephemeral message support for send and receive by @jlucaso1 in #450
  • fix: recognize own LID devices as self in DM device partitioning by @jlucaso1 in #451
  • refactor: return SendResult from status send methods by @jlucaso1 in #452
  • feat!: support LID and PN JIDs in is_on_whatsapp by @jlucaso1 in #453
  • feat!: sticker pack sending support by @jlucaso1 in #454
  • perf: sync Mutex for pending_retries, remove redundant Arc clone by @jlucaso1 in #455
  • fix: add missing context to warning logs by @jlucaso1 in #456
  • refactor!: API ergonomics — &[Jid], #[non_exhaustive], PartialEq/Eq by @jlucaso1 in #457
  • fix: add #[must_use] to AbortHandle and clear message queues on disconnect by @jlucaso1 in #459
  • fix: address 9 audit findings across correctness, safety, and performance by @jlucaso1 in #460
  • refactor: simplify Dockerfile with cargo-chef and Alpine by @jlucaso1 in #461
  • fix: per-device session locks in DM send path (#462) by @jlucaso1 in #463
  • fix: use bare Signal address for 1:1 DM recipients by @jlucaso1 in #464
  • fix!: decode key-index-list to filter stale devices from device registry by @jlucaso1 in #469
  • perf!: reduce allocations in upload, send, and Jid paths by @jlucaso1 in #471
  • feat: complete tctoken/cstoken privacy token lifecycle matching WA Web by @jlucaso1 in #473
  • feat: add Signal protocol feature API and consolidate internal helpers by @jlucaso1 in #474
  • fix!: migrate stale PN sessions to LID to fix NoSession decryption failures by @jlucaso1 in #475
  • feat: buffered download fallback when streaming is unavailable by @jlucaso1 in #476
  • fix: reduce log noise during reconnect and offload buffered decrypt by @jlucaso1 in #477
  • refactor: architecture audit — cache bounds, circuit breaker, correctness fixes by @jlucaso1 in #478
  • fix: sync own device list at login and parse server key-index in usync by @jlucaso1 in #479
  • feat: unknown device detection and deferred device sync by @jlucaso1 in #480
  • feat: portable-atomic for 32-bit targets and QR rotation safety guard by @jlucaso1 in #481
  • fix: route session migration through signal cache instead of backend by @jlucaso1 in #482
  • fix: normalize sender key address to bare JID for group messages by @jlucaso1 in #483
  • fix: revert DM send to bare recipient JID (server-side fanout) by @jlucaso1 in #484
  • fix: eliminate read-loop blocking and keepalive blind spots by @jlucaso1 in #485
  • refactor: generic WebSocket transport with from_websocket API by @jlucaso1 in #486
  • feat!: unified LID-PN lookup via get_lid_pn_entry(&Jid) by @jlucaso1 in #487
  • fix: group description parsing, ephemeral trigger, participating roundtrip, business hour types by @jlucaso1 in #488
  • fix: invalidate sender key cache on device changes and handle identity notifications by @jlucaso1 in #489
  • fix: complete identity change handler to match WA Web by @jlucaso1 in #490
  • chore: update nightly to 2026-04-05 and fix new clippy warnings by @jlucaso1 in #491
  • fix!: persist server_has_prekeys flag matching WA Web by @jlucaso1 in #492
  • fix: process group messages from unknown devices instead of discarding by @jlucaso1 in #493
  • fix: validate phash from server ack to detect stale device lists by @jlucaso1 in #494
  • chore: reduce duplication and unnecessary allocations by @jlucaso1 in #495
  • chore: add SenderKeyName::from_jid() and avoid clones in parse_message_info by @jlucaso1 in #496
  • perf: Reduce duplication and allocation churn by @jlucaso1 in #497
  • chore(deps): update sha1, sha2, hkdf, hmac to digest 0.11 ecosystem by @jlucaso1 in #498
  • perf: reduce duplication and unnecessary allocations by @jlucaso1 in #499
  • perf: zero-alloc Jid dedup and session lock helper by @jlucaso1 in #500
  • bench: add full send/receive pipeline benchmarks by @jlucaso1 in #501
  • perf: zero-copy ciphertext serialization and DRY send pipeline by @jlucaso1 in #502
  • perf!: immutable load_sender_key and auto-reserve marshal by @jlucaso1 in #503
  • fix: handle messages via PDO instead of silently dropping by @mcaldas in #506
  • fix: PDO cache key mismatch and add response guards by @jlucaso1 in #509
  • perf: lazy ciphertext in SignalMessage, eliminate redundant allocation by @jlucaso1 in #510
  • fix: codebase audit with bug fixes, race condition mitigations, and perf improvements by @jlucaso1 in #511
  • perf!: use CompactString for NodeValue, NodeContent, and Jid.user by @jlucaso1 in #512
  • perf!: yoke zero-copy node decoding and Jid Server enum by @jlucaso1 in #513
  • perf!: replace Cow with NodeStr for inline decoded strings by @jlucaso1 in #514
  • perf!: Arc event bus to eliminate deep clones on dispatch by @jlucaso1 in #515
  • perf!: merge message_enqueue_locks + message_queues into ChatLane by @jlucaso1 in #516
  • perf: single-buffer ProtocolAddress + reusable hot-loop address construction by @jlucaso1 in #518
  • perf!: reduce hot-path allocations in receipt, send, and signal paths by @jlucaso1 in #519
  • perf!: Arc across message, retry, and PDO paths by @jlucaso1 in #520
  • fix: suppress ack errors for all transport-unavailable conditions by @jlucaso1 in #521
  • perf!: zero-copy receive pipeline and take-ownership session cache by @jlucaso1 in #522
  • feat!: comprehensive group feature improvements by @jlucaso1 in #527
  • perf!: reduce startup allocations by ~21% and peak heap by ~31% by @jlucaso1 in #525
  • chore(deps): bump rand from 0.10.0 to 0.10.1 by @dependabot[bot] in #531
  • chore(deps): bump rustls from 0.23.37 to 0.23.38 by @dependabot[bot] in #530
  • chore(deps): bump compact_str from 0.8.1 to 0.9.0 by @dependabot[bot] in #528
  • fix: WA Web-compliant DM multi-device fanout with phash validation by @jlucaso1 in #524
  • fix: centralize timestamp handling via wacore::time and fix signed parsing by @Salientekill in #532
  • feat!: replace history sync events with lazy blob + perf optimizations by @jlucaso1 in #533
  • feat(groups): add typed membership request variants (correct wire format) by @Salientekill in #534
  • chore(deps): bump aes from 0.8.4 to 0.9.0 by @dependabot[bot] in #529
  • feat: add extension points for proxy and custom TLS support by @jlucaso1 in #536
  • chore(deps): bump libsqlite3-sys from 0.35.0 to 0.36.0 by @dependabot[bot] in #431
  • fix: replace tokio timeout with runtime-agnostic timeout in phash validation by @jlucaso1 in #537
  • feat(wacore-binary): zero-copy Serialize for NodeRef type family by @jlucaso1 in #539
  • feat(prekeys): expose public refresh_prekeys() for device migration by @aldoeliacim in #538
  • perf: eliminate fmt::Write dispatch from JID string formatting by @jlucaso1 in #540
  • feat(status): add send_reaction for status likes by @jlucaso1 in #541
  • perf: skip string classification for strings longer than PACKED_MAX by @jlucaso1 in #542
  • perf: unify single/double-byte token maps into single PHF lookup by @jlucaso1 in #543
  • perf!: replace PHF+SipHash token lookup with hashify PTHash (FNV-1a) by @jlucaso1 in #544
  • perf: avoid unnecessary clone and pre-allocate Vecs in hot paths by @jlucaso1 in #545
  • perf: trim unused features, deps, and gate prost-build behind feature by @jlucaso1 in #547
  • refactor: targeted single-device DM retry (matches WA Web) by @Copilot in #549
  • fix: WA Web-compliant DM retry handler (chat/requester separation + alternate key lookup) by @jlucaso1 in #550
  • perf: integration benchmarks + allocation optimizations (-23% total, -43% connect allocs) by @jlucaso1 in #551
  • perf: zero-copy frame send, direct prekey encoding, covariant AttrsRef by @jlucaso1 in #552
  • perf: split notification handler, boxed-slice children, interest-based props by @jlucaso1 in #553
  • perf: signal store hot path - lightweight PN, cache-first identity, reusable flush buffer by @jlucaso1 in #554
  • perf: pre-encode acks, Bytes for sessions, [u8;32] for identities by @jlucaso1 in #555
  • WA Web compliance: retry flow + group/status session rebuild by @jlucaso1 in #559
  • Graceful shutdown for detached tasks (fixes WASM setTimeout leaks) by @jlucaso1 in #560
  • feat: pluggable SignalCryptoProvider for AES-CBC/GCM + HMAC-SHA256 by @jlucaso1 in #561
  • feat: incoming call events + member label update by @jlucaso1 in #562
  • fix: status reactions via send_message with correct E2EE wire format by @jlucaso1 in #563
  • fix: participant change success check + phone/username mixins by @jlucaso1 in #564
  • fix: cache-aside fallback in get_lid_pn_entry by @jlucaso1 in #565
  • Align enum JSON discriminators with wire tags via StringEnum by @jlucaso1 in #566
  • Unify enum wire-tag derives under #[derive(WireEnum)] by @jlucaso1 in #567
  • fix(status): use LID addressing, skip unresolvable recipients by @jlucaso1 in #568
  • fix(status): drop on reactions and revokes by @jlucaso1 in #569
  • perf: 11 allocation trims from hot-path audit by @jlucaso1 in #570
  • fix(receipt): flush in-flight delivery receipts on disconnect by @jlucaso1 in #573
  • feat(mex): notification dispatcher + consolidated doc-id registry by @jlucaso1 in #574
  • refactor(receipt): extract FlushScope primitive; fix PDO leak via shutdown signal by @jlucaso1 in #576
  • fix: concurrent disconnect hang (flush_scope counter leak + cleanup_connection_state socket teardown) by @jlucaso1 in #577
  • perf(message): off-load LID-PN persist + migrations from the decrypt hot path by @jlucaso1 in #578
  • fix(send): close LID↔PN zombie path for group prekey 406 latency spikes by @jlucaso1 in #579
  • chore: audit follow-ups (perf, cleanup, helpers) by @jlucaso1 in #584
  • chore(deps): bump yoke from 0.7.5 to 0.8.2 by @dependabot[bot] in #583
  • fix(pdo): align PDO recovery + UndecryptableMessage dispatch with WA Web by @jlucaso1 in #585
  • feat(device)!: DevicePropsOverride builder, align DEVICE_PROPS with WA Web by @jlucaso1 in #586
  • fix(http): raise UreqHttpClient body cap from ureq's 10 MiB default by @jlucaso1 in #587
  • fix(time): route all now() calls through wacore::time; enforce via clippy by @jlucaso1 in #588
  • fix(decrypt): fall through when comes alongside by @jlucaso1 in #589
  • perf: reduce cache and channel pre-allocations by @jlucaso1 in #590
  • [codex] fix e2e reconnect and receipt flakes by @jlucaso1 in #591
  • fix(pair-code)!: derive companion_platform_{id,display} from DeviceProps by @jlucaso1 in #592
  • fix(pair): WA Web compliant pairing QR + companion_platform_{id,display} by @jlucaso1 in #593
  • fix(pair,props): enforce ADV HMAC verify and round-trip children by @jlucaso1 in #594
  • fix(pair): emit empirically correct companion_platform_id for Android by @jlucaso1 in #595
  • feat(client): add ClientProfile for noise-handshake identity by @jlucaso1 in #596
  • refactor!: preserve typed error sources across the workspace by @jlucaso1 in #597
  • feat(noise): implement Noise_IK + XXfallback for WA-Web parity by @jlucaso1 in #598
  • feat(call): detect group calls via offer_notice and group-jid attrs by @Salientekill in #599
  • fix(pair)!: map Android PlatformType to Chrome, drop Unknown variant by @jlucaso1 in #601
  • fix(blocklist): include LID + pn_jid in block IQ (modern WA) by @Salientekill in #600
  • fix(proto_helpers): detect view_once inline flag and nested wrappers by @jlucaso1 in #602
  • fix(send): always distribute SKDM on first group send + stop wiping tracker on identity change by @jlucaso1 in #603
  • fix(send): close SKDM flow gaps for forward secrecy and cache hygiene by @jlucaso1 in #604
  • fix(lid_pn): WA Web compliant signal address for Hosted JIDs by @jlucaso1 in #605
  • fix(lid_pn): keep status@broadcast resolving to @lid by @jlucaso1 in #609
  • refactor(time): split monotonic clock from wall clock by @jlucaso1 in #611
  • fix(portable_cache): add iter() to mirror moka::Cache by @jlucaso1 in #612
  • perf(send): parallelize group encrypt fan-out + adjacent wins by @jlucaso1 in #610
  • perf(events): share wa::Message via Arc end-to-end (zero deep-clone on dispatch) by @Salientekill in #613
  • feat(history_sync): expose peer_data_request_session_id on LazyHistorySync by @jlucaso1 in #614
  • feat(groups): return full GroupMetadata from create_group / community.create by @jlucaso1 in #615
  • feat(message_edit): decrypt secretEncryptedMessage MESSAGE_EDIT envelope by @jlucaso1 in #618
  • chore(release): bump workspace to 0.6.0 by @jlucaso1 in #619
  • ci(release): install cargo-release via taiki-e action by @jlucaso1 in #620