Skip to main content

Bug Fix

PDO placeholder-resend: at-most-once per message (#841) A peer device with cloned Signal state could redeliver the same undecryptable message every ~11 seconds. Each copy triggered a PDO placeholder-resend request to our own phone — the pdo_pending_requests dedup only covered in-flight requests, so it emptied the moment the phone answered (~800 ms), and the next copy immediately opened a new one. In a three-hour storm this produced ~700 redundant requests with no benefit: the phone had already answered without content, so re-asking could not produce anything new. One request per message. A new pdo_requested memo cache (24h TTL, 512 entries) gates send_pdo_placeholder_resend_request — mirroring WAWebNonMessageDataRequestPlaceholderMessageResendUtils, which uses a session-lifetime set for the same purpose. The memo slot is released if the send itself fails, so a transient error does not permanently block recovery. A content-less phone response leaves the memo in place (the phone has nothing to share for this message; re-asking on the next redelivery cannot help). Skip migration retry decrypt when nothing moved. migrate_signal_sessions_on_lid_discovery now returns bool indicating whether any sessions moved into a LID slot. When it returns false, the subsequent retry decrypt in try_pn_to_lid_migration_decrypt is skipped: the Signal state is unchanged, so the retry would fail identically and only add a second decrypt error to the log for every redelivered copy. Log-level adjustments. Three lines that fired once per redelivered copy are brought in line with WA Web’s telemetry handling:
  • “Skipping skmsg decryption” → debug (WA Web’s canDecryptNext skips silently after a retryable pkmsg failure)
  • “missing message content” on a PDO response → info (WA Web counts this outcome in telemetry only, no warning)
  • “Max retries reached” at the PDO fallback → debug (the high-retry warn! already fired on the way to the cap)
With these changes, the same storm would produce 1 PDO request, 1 decrypt error per copy, and the existing retry receipt cap — instead of thousands of WARN/ERROR lines and hundreds of peer messages.

Breaking changes

CacheConfig gains a new field (whatsapp-rust) CacheConfig now has a pdo_requested field (default: 24h TTL, 512 entries). Struct literals that spell out every field rather than using ..Default::default() will fail to compile.
// Update struct literals that do not use ..Default::default():
let config = CacheConfig {
    // your overrides ...
    ..Default::default() // pdo_requested gets its default; no action required
};
To tune the memo TTL or capacity:
use std::time::Duration;
use whatsapp_rust::{CacheConfig, CacheEntryConfig};

let config = CacheConfig {
    // Extend the TTL beyond the 24h default if you want the memo to survive
    // across longer offline gaps without re-asking the phone.
    pdo_requested: CacheEntryConfig::new(Some(Duration::from_secs(48 * 3600)), 512),
    ..Default::default()
};
MemoryDiagnostics gains a new field (whatsapp-rust) MemoryDiagnostics now has a pdo_requested: u64 field. Code that exhaustively pattern-matches or constructs MemoryDiagnostics directly will need updating.