feat: WA Web phash parity — usync device_hash (#3), group-metadata phash (#7), bcl hash validation (#6) - #679
Conversation
…iceList) WA Web's usync device query carries a per-user device_hash so the server returns only CHANGED device lists (omitting unchanged users). We never sent it: our query paths either query on cache-miss (no hash) or invalidate-then-full-query. - DeviceListSpec gains optional per-user (device_hash, ts) hints; build_iq emits <user jid="..."><devices device_hash="2:.." ts="N"/></user> when present, bare otherwise. - sync_own_device_list stops invalidating; it reads the cached own PN/LID device_hash and sends it, so an unchanged own device list is skipped server-side on every reconnect (matches syncMyDeviceList). Changed lists are returned and applied; omitted (unchanged) users keep their cache. - Extract the response-processing (LID mappings + record rebuild + batched write) into process_device_list_response, shared by get_user_devices and the own re-sync. It only touches users present in the response, so omitted users are preserved. whatsmeow omits this optimization entirely; it is WA-Web-specific. The change is fail-safe: a wrong/absent hash just yields a full list (today's behavior); the only risk is the omitted-user case, covered by tests. Tests: build_iq with/without device_hash; parse omits an unchanged user; and a merge-safety test proving an omitted user's cached devices are preserved.
…ed (WA Web)
WA Web's queryGroup carries a phashV2 of the cached participant list so the
server answers "not-modified" (absent <group>) for an unchanged group. We always
re-queried in full.
- GroupQueryIq gains an optional phash; build_iq emits
<query request="interactive" phash="2:.."/>. Its Response becomes
GroupInfoOutcome { Full(Box<GroupInfoResponse>), NotModified } — NotModified
when the server omits <group>.
- query_info persists the resolved GroupInfo (serde) via a new ProtocolStore
group-metadata blob store, and on a later cache miss sends the participant
phash; on NotModified it reuses the persisted metadata instead of re-parsing.
- New ProtocolStore::get_group_metadata/put_group_metadata with no-op defaults
(non-breaking for other backends, incl. the test MockBackend); sqlite gets a
group_metadata table + migration, in_memory a HashMap.
The participant-phash hash form is verified by code-reading only; the change is
fail-safe — a wrong/absent hash just yields a full group (today's behavior), so
the worst case is no skip, never a stale group.
Tests: build_iq with/without phash; parse Full vs NotModified; group_metadata
round-trip (sqlite + in_memory).
…clHash) WA Web validates the broadcast-contact-list hash on a self-synced broadcast/status: the sending device puts a phashV2 of the recipients in deviceSentMessage.phash; the receiver recomputes over its <participants> view and, on mismatch, drops the message. We add the recompute + a warning on divergence, but do NOT drop (the exact participant hash form is verified by code-reading only, so dropping could lose legitimate messages until validated against a real DSM). This is fail-safe and can be flipped to a drop later. - MessageUtils::validate_bcl_hash(participants, expected) compares phashV2. - parse_message_info collects <participants><to jid> into MessageInfo.bcl_participants for broadcast/status stanzas only (a group's <participants> is the device fanout, not a bcl, and is skipped). - handle_decrypted_plaintext logs a warning when a device-sent broadcast's phash disagrees with our recomputed hash (before unwrapping the DSM). Tests: validate_bcl_hash match/reject; broadcast populates bcl_participants; group fanout participants do not.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR adds persisted group metadata and phash-aware group queries, validates broadcast-contact-list hashes for broadcast/status messages, and optimizes device-list syncs with per-user hash hints and merge-safe response processing. ChangesGroup Metadata Caching & Broadcast BCL Validation
Device List Sync Optimization
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Benchmark Results1 regression(s) detected (>2% threshold):
66 unchanged benchmark(s)
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@wacore/src/messages.rs`:
- Around line 57-64: Update the doc comment on validate_bcl_hash to remove the
misleading parenthetical about empty participants and clarify behavior: state
that validate_bcl_hash calls participant_list_hash and returns true only when
the computed hash equals expected (which includes the deterministic hash
computed for an empty participant list), or replace the parenthetical with the
suggested phrasing "returns true when the computed hash matches expected
(including for empty participant lists)". Ensure the comment refers to
validate_bcl_hash and participant_list_hash so readers can find the
implementation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 706ec815-c684-4f36-b90b-4306ff2185f4
📒 Files selected for processing (14)
src/features/groups.rssrc/message.rssrc/pdo.rssrc/usync.rsstorages/sqlite-storage/migrations/2026-06-01-000000_add_group_metadata/down.sqlstorages/sqlite-storage/migrations/2026-06-01-000000_add_group_metadata/up.sqlstorages/sqlite-storage/src/schema.rsstorages/sqlite-storage/src/sqlite_store.rswacore/src/iq/groups.rswacore/src/iq/usync.rswacore/src/messages.rswacore/src/store/in_memory.rswacore/src/store/traits.rswacore/src/types/message.rs
The doc said empty participants pass "trivially", which is misleading: validate_bcl_hash calls participant_list_hash, and an empty list hashes to its own deterministic value — so it matches only when `expected` is that value, not unconditionally. Reword and link participant_list_hash so the behavior and impl are clear.
Follow-up to #678. The phash audit found three WA Web hash mechanisms we didn't fully replicate. This implements all three, faithful to WA Web where it's safe to be. None was a one-line "add a hash attr": our architecture (query-on-cache-miss + notification-driven updates) lacks the "re-query a known entity with its cached hash" pattern WA Web's hashes rely on, so each needed a small integration addition.
Shared building block:
MessageUtils::participant_list_hash(phashV2, fixed to standard base64 in #678).#3 — usync
device_hashon own-device re-sync (feat(usync))WA Web sends a per-user
device_hashin the usync device query so the server returns only CHANGED lists. We never sent it (we query on cache-miss, or invalidate-then-full-query). Nowsync_own_device_listsends the cached owndevice_hash(matchingsyncMyDeviceList), so an unchanged own device list is skipped server-side on every reconnect.DeviceListSpecgains optional per-user(device_hash, ts)hints →<user><devices device_hash="2:.." ts="N"/></user>.process_device_list_response, shared withget_user_devices; it only touches users present in the response, so omitted (unchanged) users keep their cache.whatsmeow omits this optimization entirely; it's WA-Web-specific.
#7 — group-metadata participant phash (
feat(group))WA Web's
queryGroupcarries a phashV2 of the cached participants so the server answers "not-modified" (absent<group>) for an unchanged group. We always re-queried in full.GroupQueryIqgains an optional phash;ResponsebecomesGroupInfoOutcome { Full(Box<..>), NotModified }.query_infopersists the resolvedGroupInfo(newProtocolStoregroup-metadata blob store: sqlite table + migration, in-memory map, no-op trait defaults for other backends) and on a later cache miss sends the participant phash; onNotModifiedit reuses the persisted metadata.#6 — incoming bcl hash validation (
feat(recv))WA Web validates a self-synced broadcast/status: the sending device puts a phashV2 of the recipients in
deviceSentMessage.phash; the receiver recomputes and drops on mismatch.validate_bcl_hash+MessageInfo.bcl_participants(populated from<participants><to>for broadcasts only; a group's<participants>is the device fanout, not a bcl).handle_decrypted_plaintextlogs a warning on divergence but does not drop — the exact participant hash form is verified by code-reading only, so dropping could lose legitimate messages until validated against a real DSM. Can be flipped to a drop later.Risk framing
Tests
DeviceListSpecbuild with/withoutdevice_hash; parse omits an unchanged user; merge-safety (omitted user's cache preserved).FullvsNotModified; group-metadata store round-trip (sqlite + in-memory).validate_bcl_hashmatch/reject; broadcast populatesbcl_participants; group fanout does not.cargo clippy --all-targets -- -D warningsclean; wacore + whatsapp-rust + sqlite-storage suites green.