Skip to main content

Performance

History sync: gate prost decode behind secret-presence scan (#836) extract_conversation_fields previously decoded every HistorySyncMsg through prost — a 30-field struct with String allocations — to discover that most messages carry no message_secret and yield nothing useful. The CodSpeed profile showed 66% of bench_process_history_sync inside that decode path, with 56K allocations and 14.3 MB allocated per run on a 500-conversation × 40-message fixture. A shallow varint walk now runs before prost as a fast-path gate. It checks WebMessageInfo.message_secret and Message.message_context_info.message_secret; messages with no secret at any level are discarded immediately — no struct decode, no allocation. The scan mirrors prost merge semantics (repeated field occurrences count, malformed bytes degrade to the same skip a failed decode produces), and the flag logic for forwarded/poll/bot messages still runs inside prost on the subset that passes the scan. Real-world impact is larger than the bench numbers suggest. The fixture is secret-dense (2 of 3 messages carry a secret). Production InitialBootstrap blobs are secret-sparse, so the pre-scan skips proportionally more full decodes there. Benchmark (500 convos × 40 messages, 20k messages, secret-dense fixture):
MetricBeforeAfter
Allocations / run56,02039,520 (−29.5%)
Allocated bytes / run14.97 MB14.32 MB (−4.3%)
Instructions (core-pinned)2,358.7M2,301.7M (−2.4%)
Shared conversation id (#836) HistoryMsgSecretRecord.chat_id is now Arc<str>, allocated once per conversation and reference-counted into every record within that conversation. On the bench fixture: 10k clones → 500. msg_id switches to CompactString (inline up to 24 bytes for the typical 20–22 character WA message IDs), and secret switches to SecretBytes (inline up to 32 bytes, heap for larger).

Type safety

Schema-pinned wire tags (#836) waproto now generates tags.rs at build time (alongside whatsapp.rs) from the compiled protobuf descriptor. The file exposes one pub mod per proto message with one pub const FIELD_NAME: u32 = N; per field. The history-sync wire walkers now reference these consts instead of magic numeric literals, and every #[prost(tag)] literal in the hand-written mirror structs is pinned by a compile-time assert! block. If whatsapp.proto renumbers a field the consts update automatically on next build; if a field referenced by the walkers is renamed or removed, compilation fails rather than the decoder silently reading the wrong wire data.
use waproto::tags;

// Field number consts derived directly from whatsapp.proto
let _ = tags::web_message_info::KEY;           // 1
let _ = tags::web_message_info::MESSAGE;       // 2
let _ = tags::message::MESSAGE_CONTEXT_INFO;   // 35
let _ = tags::history_sync::CONVERSATIONS;     // 2
let _ = tags::history_sync::PUSHNAMES;         // 7

Breaking changes

HistoryMsgSecretRecord field types (wacore) Three fields changed type. Consumers of HistoryMsgSecretRecord need the following updates:
// chat_id: String → Arc<str>
// Borrow as &str (no move):
let chat_str: &str = &*record.chat_id;
// Clone the Arc (cheap refcount bump):
let chat_arc: Arc<str> = Arc::clone(&record.chat_id);
// Convert to owned String:
let chat_string: String = record.chat_id.to_string();

// msg_id: String → CompactString (compact_str::CompactString)
// Borrow as &str:
let id_str: &str = &*record.msg_id;
// Convert to owned String (consumes field):
let id_string: String = record.msg_id.into_string();

// secret: Vec<u8> → SecretBytes
// Borrow as &[u8]:
let key_slice: &[u8] = record.secret.as_slice();
// Convert to Vec<u8> (consumes field):
let key_vec: Vec<u8> = record.secret.into_vec();
SecretBytes stores secrets ≤32 bytes inline (no heap allocation) and falls back to Vec<u8> for larger values. It implements Deref<Target=[u8]>, From<&[u8]>, From<Vec<u8>>, PartialEq, Eq, and Debug. The INLINE_CAP: usize = 32 constant is public. waproto build process The generated src/whatsapp.rs is no longer committed to the repository. It now lives exclusively in OUT_DIR. Normal cargo build invocations are unaffected. The generate feature flag is removed. prost-build, heck, and prost-types are now unconditional [build-dependencies]. If you have --features generate in any build scripts, remove it. To regenerate after modifying whatsapp.proto, update the descriptor and rebuild:
scripts/regenerate-proto-desc.sh   # writes waproto/src/whatsapp.desc + .sha256
cargo build -p waproto             # regenerates whatsapp.rs and tags.rs in OUT_DIR
The build verifies the descriptor’s SHA-256 against whatsapp.desc.sha256 and aborts with a clear message on mismatch.