fix(send): hoist messageContextInfo to outer in DeviceSentMessage (WA Web parity) - #696
Conversation
… Web parity) When syncing a sent DM to our own other devices we wrap it in a DeviceSentMessage. We were placing `messageContextInfo` (which carries the `messageSecret` used to decrypt later add-ons — reactions/edits/polls) on the INNER wrapped message and leaving the outer message empty. WhatsApp Web's `WAWebDeviceSentMessageProtoUtils.wrapDeviceSentMessage` (used by `MsgCreateDeviceStanza`) does the opposite: it hoists `messageContextInfo` onto the OUTER message and clears it from the inner copy. Our receive path (`unwrap_device_sent` + `merge_dsm_context`) already mirrors WA Web's `unwrapDeviceSentMessage` (inner-first, outer fallback), so our reader assumed the WA Web shape while our writer never produced it. Add `wrap_device_sent` (inverse of `unwrap_device_sent`) that hoists the context to the outer message and clears the inner copy; `prepare_dm_stanza` builds the own-device DSM through it. The recipient and group paths are unchanged (the recipient message keeps its context inline, exactly as WA Web sends it).
|
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 (2)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds ChangesDevice-sent Message Wrapper
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
🚥 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 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0d8931d74f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let context = message.message_context_info.take(); | ||
| wa::Message { | ||
| message_context_info: context, |
There was a problem hiding this comment.
Preserve non-secret DSM context fields
When an own-device DM already carries MessageContextInfo fields other than the WA-Web-hoisted subset, moving the entire context to the outer DSM drops those fields after receive: unwrap_device_sent delegates to merge_dsm_context, whose outer-only path copies only message_secret, association, limit sharing, thread IDs, and bot metadata. For example, src/send.rs builds pin/unpin messages with message_add_on_duration_in_secs; after this wrapper change, our other devices will unwrap the DSM without that duration, whereas the previous inner-context shape preserved it. Either keep unsupported fields on the inner copy or extend the DSM merge to round-trip the fields this sender can place in MessageContextInfo.
Useful? React with 👍 / 👎.
Benchmark Results67 unchanged benchmark(s)
|
The hoist in `wrap_device_sent` moves the whole `message_context_info` onto the outer DSM message and clears the inner copy. The receive path's outer-only merge branch (`merge_dsm_context`, inner None) rebuilt only the subset it explicitly merges (message_secret, association, limit_sharing_v2, thread_id, bot_metadata), dropping every other field — e.g. the `message_add_on_duration_in_secs` that pin messages place in MessageContextInfo. Our other devices would then unwrap a pin without its duration. The previous inner-context shape happened to preserve it. Restore the full hoisted context on the outer-only unwrap branch (the sender moved the entire context there, so we move all of it back). The wire shape on send is unchanged (still WA Web's hoist-to-outer + clear-inner).
Problem
When a sent DM is synced to our own other devices, the message is wrapped in a
DeviceSentMessage. We were placingmessageContextInfo— which carries themessageSecretused to decrypt later add-ons (reactions, edits, poll votes) against the original message — on the inner wrapped message, leaving the outer message empty.WhatsApp Web does the opposite.
WAWebDeviceSentMessageProtoUtils.wrapDeviceSentMessage(used byMsgCreateDeviceStanzaon the own-device path) hoistsmessageContextInfoonto the outer message and clears it from the inner copy:Our receive path already mirrors WA Web's
unwrapDeviceSentMessage:unwrap_device_sent+merge_dsm_contextread the secret inner-first and fall back to the outer message. So our reader already assumed the WA Web shape, while our writer never produced it — an internal inconsistency, and a wire-format divergence from WA Web on the own-device sync stanza.Fix
Add
wrap_device_sent(the inverse of the existingunwrap_device_sent) inwacore::messages, which hoistsmessage_context_infoonto the outer message and clears the inner copy, matching WA Web.prepare_dm_stanzanow builds the own-device DSM through it.The recipient and group paths are unchanged: the recipient message keeps its
messageContextInfoinline, exactly as WA Web sends it. Only the own-device DSM envelope changes shape.Tests
New unit tests in
wacore(messages::device_sent_tests):wrap_hoists_context_to_outer_on_wire— encodes then decodes the wrapped message and asserts the secret lands on the outer message and the inner copy carries nomessageContextInfo.wrap_without_context_leaves_outer_empty— no context in, no context hoisted.wrap_then_unwrap_round_trips_secret—wrap_device_sent→unwrap_device_sentrecovers the original content and the secret.Breaking
None.
prepare_dm_stanza's signature is unchanged;wrap_device_sentis a new additive helper. The DSM wire shape changes to match WA Web; our own reader already handled it (and still does, via the round-trip test).