Skip to content

Chore reduce allocations - #199

Merged
jlucaso1 merged 6 commits into
mainfrom
chore-reduce-allocations
Dec 25, 2025
Merged

Chore reduce allocations#199
jlucaso1 merged 6 commits into
mainfrom
chore-reduce-allocations

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Dec 25, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Performance

    • Reduced reallocations and memory churn across batching, session handling, and message processing for more efficient runtime and lower memory use.
  • New Features

    • Added public session-state APIs for finer control over session recovery and manipulation.
    • Introduced JID-aware attribute handling in the protocol layer.
  • Bug Fixes

    • Improved session-state consistency in error paths to prevent state corruption.
  • Tests

    • Updated and tightened protocol tests and test data for clearer assertions.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 25, 2025

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Pre-allocation and borrow/clone reductions were added across multiple modules; a zero-copy-aware ValueRef was introduced for binary node attributes (strings or JIDs), decoder/encoder/attrs updated to use it; session-state APIs were added and session-cipher error paths now restore taken state.

Changes

Cohort / File(s) Summary
Vec capacity & minor alloc tweaks
src/appstate_sync.rs, src/client.rs, src/prekeys.rs, src/session.rs, src/message.rs
Pre-allocate Vecs with with_capacity(...) and replace some Vec::new() / pushes with capacity hints; borrow ciphertext slices instead of cloning in message decryption paths.
Session batching & API signature
src/client/sessions.rs, src/session.rs
fetch_and_establish_sessions now accepts &[Jid] (was Vec<Jid>); callers updated to pass slices/batches without intermediate Vec allocation; related batching code adjusted.
Cache / clone reductions & sender-key prefixing
src/client/device_registry.rs, src/lid_pn_cache.rs, src/client/sender_keys.rs
Avoid duplicate clones by using clone_from and single cloned entries for map inserts; precompute LID/PN prefixes to prevent repeated allocations in sender-key filtering.
Message refactors & helpers
src/message.rs
Use capacity hints for enc-node collections; unify user/LID matching via matches_user_or_lid helper usage; add in-function helpers for AppStateSyncKey extraction to reduce nested unwraps.
Handler constructors removed / Default usage
src/handlers/*.rs (basic.rs, ib.rs, iq.rs, message.rs, notification.rs, receipt.rs)
Remove trivial new() constructors and rely on #[derive(Default)]; add extract_device_ids helper in notification handler and use in tests.
ValueRef public API & Node/Attrs changes
wacore/binary/src/node.rs, wacore/binary/src/attrs.rs
Add public ValueRef<'a> enum (String
Decoder/Encoder integration
wacore/binary/src/decoder.rs, wacore/binary/src/encoder.rs
Decoder: new read_value() returning Option<ValueRef> (JID-aware); read_attributes and attribute parsing use ValueRef. Encoder: convert ValueRef to string via to_string_cow() when writing attributes.
libsignal session-state APIs and cipher error handling
wacore/libsignal/src/protocol/state/session.rs, wacore/libsignal/src/protocol/session_cipher.rs
Add take_session_state, previous_session_count, take_previous_session, restore_previous_session; session_cipher takes ownership of current session state and restores it on error paths instead of cloning.
Store error centralization & persistence adjustments
wacore/src/store/error.rs, src/store/error.rs, src/store/persistence_manager.rs
Add db_err helper converting errors -> StoreError::Database; re-export db_err and switch backend error mappings to map_err(db_err) throughout persistence manager.
Tests and small value checks
wacore/tests/binary_protocol_test.rs, src/* tests
Tests updated to use explicit .as_str().expect(...) where appropriate and several tests changed phone/JID fixtures to new values (559999999999).

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble buffers, smooth each clone away,

ValueRef keeps JIDs cozy as they stay.
Sessions taken, guarded, then returned with care—
Fewer hops, lighter hops, I bounce on air! 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Chore reduce allocations' accurately reflects the primary objective of the changeset, which involves performance optimizations through pre-allocation and allocation reduction across multiple files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore-reduce-allocations

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fbdef3 and dbf4533.

📒 Files selected for processing (14)
  • src/client.rs
  • src/client/sessions.rs
  • src/handlers/basic.rs
  • src/handlers/ib.rs
  • src/handlers/iq.rs
  • src/handlers/message.rs
  • src/handlers/notification.rs
  • src/handlers/receipt.rs
  • src/message.rs
  • src/pdo.rs
  • src/store/error.rs
  • src/store/persistence_manager.rs
  • wacore/binary/src/jid.rs
  • wacore/src/store/error.rs
💤 Files with no reviewable changes (4)
  • src/handlers/receipt.rs
  • src/handlers/message.rs
  • src/handlers/basic.rs
  • src/handlers/iq.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Never modify Device state directly; use DeviceCommand + PersistenceManager::process_command() for state changes
For read-only Device state access, use PersistenceManager::get_device_snapshot()
All blocking I/O (such as ureq calls) and heavy CPU-bound tasks (like media encryption) MUST be wrapped in tokio::task::spawn_blocking to avoid stalling the async runtime
Use Client::chat_locks to serialize per-chat operations in asynchronous code
Use thiserror for custom domain-specific errors (e.g., SocketError) and anyhow::Error for functions with multiple failure modes
Avoid .unwrap() and .expect() outside of tests and unrecoverable logic paths
Use the Downloadable trait in wacore/src/download.rs for implementing generic media download interfaces across message types
Always refresh MediaConn if it's expired before using it to get current media servers and auth tokens
Run cargo fmt before finalizing a feature or fix
Run cargo clippy --all-targets before finalizing a feature or fix
Run cargo test --all before finalizing a feature or fix

Files:

  • src/store/error.rs
  • wacore/src/store/error.rs
  • src/handlers/notification.rs
  • wacore/binary/src/jid.rs
  • src/pdo.rs
  • src/message.rs
  • src/store/persistence_manager.rs
  • src/handlers/ib.rs
  • src/client/sessions.rs
  • src/client.rs
wacore/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

wacore crate must remain platform-agnostic and have no dependencies on Tokio or specific databases

Files:

  • wacore/src/store/error.rs
  • wacore/binary/src/jid.rs
🧠 Learnings (1)
📚 Learning: 2025-12-25T03:36:19.692Z
Learnt from: CR
Repo: jlucaso1/whatsapp-rust PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T03:36:19.692Z
Learning: Applies to wacore/**/*.rs : `wacore` crate must remain platform-agnostic and have no dependencies on Tokio or specific databases

Applied to files:

  • src/store/error.rs
🧬 Code graph analysis (5)
src/store/error.rs (1)
wacore/src/store/error.rs (1)
  • db_err (35-37)
src/message.rs (2)
wacore/libsignal/src/protocol/protocol.rs (1)
  • ciphertext (456-458)
wacore/libsignal/src/protocol/group_cipher.rs (1)
  • group_decrypt (152-235)
src/store/persistence_manager.rs (3)
wacore/src/store/error.rs (1)
  • db_err (35-37)
wacore/src/store/traits.rs (1)
  • clear_skdm_recipients (83-83)
storages/sqlite-storage/src/sqlite_store.rs (1)
  • clear_skdm_recipients (1860-1879)
src/client/sessions.rs (1)
wacore/binary/src/jid.rs (12)
  • pn (321-327)
  • user (222-222)
  • user (297-299)
  • user (441-443)
  • server (223-223)
  • server (300-302)
  • server (444-446)
  • device (224-224)
  • device (303-305)
  • device (447-449)
  • pn_device (348-355)
  • is_ad (227-232)
src/client.rs (2)
wacore/src/reporting_token.rs (1)
  • test_jid (959-961)
wacore/binary/src/jid.rs (1)
  • pn (321-327)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: benchmark
  • GitHub Check: Build & Test
🔇 Additional comments (19)
wacore/src/store/error.rs (1)

32-37: LGTM! Clean error handling helper.

The db_err helper function is well-designed: it's generic, inlined for performance, and provides a concise way to map any Display error to StoreError::Database. The documentation clearly explains its purpose and usage pattern.

src/store/error.rs (1)

2-2: LGTM! Proper re-export.

The re-export of db_err makes the helper available throughout the application, consistent with the existing re-export pattern for Result and StoreError.

src/store/persistence_manager.rs (1)

1-1: LGTM! Consistent error handling throughout.

The systematic replacement of manual error mapping closures with map_err(db_err) across all backend operations improves code consistency and reduces boilerplate. All nine call sites are correctly updated, and the error handling remains functionally equivalent.

Also applies to: 23-23, 26-26, 31-31, 69-69, 141-141, 146-146, 190-190, 202-202, 210-210

wacore/binary/src/jid.rs (1)

421-426: LGTM! Clean helper method for user/LID matching.

The implementation correctly combines user matching with optional LID checking using idiomatic Rust (is_some_and). The documentation clearly explains the use case for group message participant checking.

src/handlers/notification.rs (1)

12-23: LGTM! Good refactoring to reduce duplication.

Extracting the device ID collection logic into a helper function improves maintainability and reduces code duplication between the handler and tests.

src/handlers/ib.rs (1)

17-18: LGTM! Idiomatic use of derived Default.

Using derive(Default) is the idiomatic approach and aligns with the pattern applied to other handlers in this PR.

src/client.rs (2)

351-359: LGTM! Handler instantiation now uses derived Default.

The direct type construction (e.g., MessageHandler) works because the handlers now use derive(Default). This is cleaner than calling explicit constructors.


1229-1229: LGTM! Good pre-allocation optimization.

Pre-allocating to_request with capacity based on missing.len() reduces reallocations when building the request list.

src/client/sessions.rs (4)

55-57: LGTM! Good pre-allocation optimization.

Pre-allocating jids_needing_sessions with capacity equal to resolved_jids.len() reduces reallocations during the filtering loop.


84-86: LGTM! Efficient slice passing eliminates unnecessary clone.

Passing the slice directly to fetch_and_establish_sessions instead of calling to_vec() avoids an unnecessary allocation per batch.


96-96: LGTM! Signature change reduces allocations.

Changing from Vec<Jid> to &[Jid] eliminates unnecessary allocations at all call sites. The function body correctly iterates over the slice (line 115: for jid in jids gives &Jid items when jids: &[Jid]).


195-197: LGTM! Clever allocation avoidance using slice::from_ref.

Using std::slice::from_ref(&primary_phone_jid) to create a single-element slice is more efficient than allocating a Vec just to pass one JID.

src/message.rs (7)

380-381: LGTM: Effective pre-allocation reduces reallocations.

Using Vec::with_capacity(all_enc_nodes.len()) avoids multiple reallocations during the subsequent loop. While the capacity hint may slightly over-allocate (since some nodes go to custom handlers), this is preferable to incremental growth.


550-556: LGTM: Borrowing eliminates unnecessary clone.

Changing from b.clone() to b eliminates an allocation. The borrowed slice &[u8] is valid for the loop iteration scope and safely passed to the message parsing functions below.


561-575: LGTM: Direct slice passing aligns with borrowed ciphertext.

Passing ciphertext directly (which is now &[u8]) to try_from is correct and eliminates the need for .as_slice(). This works because ciphertext is already a borrowed slice from Line 550.


849-855: LGTM: Consistent borrowing pattern for group decryption.

This mirrors the session batch optimization (lines 550-556), borrowing the ciphertext slice instead of cloning. The borrowed data is passed to group_decrypt below, eliminating an unnecessary allocation.


875-875: LGTM: Direct slice usage consistent with refactor.

Passing the borrowed ciphertext directly to group_decrypt is correct and aligns with the changes at Line 849-850.


1027-1027: LGTM: Unified identity check improves consistency.

Using matches_user_or_lid helper consolidates the logic for checking if a JID matches the own user (via PN or LID). This DRY improvement is applied consistently across status broadcast (Line 1027), group messages (Line 1055), and DM checks (Line 1065).


1127-1176: LGTM: Helper extraction improves readability and borrows where possible.

The extract_key_components helper with the KeyComponents struct clarifies the extraction logic and uses borrowed slices for key_id and data. The fingerprint_bytes field still allocates (via encode_to_vec()) because it needs to be encoded, which is unavoidable. This refactor makes the code more maintainable while reducing allocations where feasible.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Dec 25, 2025

Copy link
Copy Markdown

🐰 Bencher Report

Branchchore-reduce-allocations
Testbedubuntu-latest

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
BenchmarkEstimated Cyclescycles x 1e3InstructionsinstructionsL1 Hitshits x 1e3LL HitshitsRAM HitshitsTotal read+writereads/writes x 1e3
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled()📈 view plot
⚠️ NO THRESHOLD
16.32 x 1e3📈 view plot
⚠️ NO THRESHOLD
7,352.00📈 view plot
⚠️ NO THRESHOLD
11.29 x 1e3📈 view plot
⚠️ NO THRESHOLD
12.00📈 view plot
⚠️ NO THRESHOLD
142.00📈 view plot
⚠️ NO THRESHOLD
11.44 x 1e3
binary_benchmark::marshal_group::bench_marshal_allocating📈 view plot
⚠️ NO THRESHOLD
263.59 x 1e3📈 view plot
⚠️ NO THRESHOLD
159,494.00📈 view plot
⚠️ NO THRESHOLD
245.93 x 1e3📈 view plot
⚠️ NO THRESHOLD
117.00📈 view plot
⚠️ NO THRESHOLD
488.00📈 view plot
⚠️ NO THRESHOLD
246.53 x 1e3
binary_benchmark::marshal_group::bench_marshal_reusing_buffer📈 view plot
⚠️ NO THRESHOLD
264.18 x 1e3📈 view plot
⚠️ NO THRESHOLD
159,594.00📈 view plot
⚠️ NO THRESHOLD
246.02 x 1e3📈 view plot
⚠️ NO THRESHOLD
124.00📈 view plot
⚠️ NO THRESHOLD
501.00📈 view plot
⚠️ NO THRESHOLD
246.65 x 1e3
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled()📈 view plot
⚠️ NO THRESHOLD
81.26 x 1e3📈 view plot
⚠️ NO THRESHOLD
50,338.00📈 view plot
⚠️ NO THRESHOLD
76.36 x 1e3📈 view plot
⚠️ NO THRESHOLD
77.00📈 view plot
⚠️ NO THRESHOLD
129.00📈 view plot
⚠️ NO THRESHOLD
76.57 x 1e3
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled()📈 view plot
⚠️ NO THRESHOLD
7.89 x 1e3📈 view plot
⚠️ NO THRESHOLD
3,025.00📈 view plot
⚠️ NO THRESHOLD
4.80 x 1e3📈 view plot
⚠️ NO THRESHOLD
10.00📈 view plot
⚠️ NO THRESHOLD
87.00📈 view plot
⚠️ NO THRESHOLD
4.90 x 1e3
binary_benchmark::unpack_group::bench_unpack_compressed📈 view plot
⚠️ NO THRESHOLD
1,143.97 x 1e3📈 view plot
⚠️ NO THRESHOLD
468,057.00📈 view plot
⚠️ NO THRESHOLD
893.99 x 1e3📈 view plot
⚠️ NO THRESHOLD
3,481.00📈 view plot
⚠️ NO THRESHOLD
6,645.00📈 view plot
⚠️ NO THRESHOLD
904.11 x 1e3
binary_benchmark::unpack_group::bench_unpack_uncompressed📈 view plot
⚠️ NO THRESHOLD
1.79 x 1e3📈 view plot
⚠️ NO THRESHOLD
779.00📈 view plot
⚠️ NO THRESHOLD
1.07 x 1e3📈 view plot
⚠️ NO THRESHOLD
4.00📈 view plot
⚠️ NO THRESHOLD
20.00📈 view plot
⚠️ NO THRESHOLD
1.09 x 1e3
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message()📈 view plot
⚠️ NO THRESHOLD
35.18 x 1e3📈 view plot
⚠️ NO THRESHOLD
11,800.00📈 view plot
⚠️ NO THRESHOLD
16.95 x 1e3📈 view plot
⚠️ NO THRESHOLD
42.00📈 view plot
⚠️ NO THRESHOLD
515.00📈 view plot
⚠️ NO THRESHOLD
17.51 x 1e3
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message()📈 view plot
⚠️ NO THRESHOLD
15.87 x 1e3📈 view plot
⚠️ NO THRESHOLD
3,818.00📈 view plot
⚠️ NO THRESHOLD
5.46 x 1e3📈 view plot
⚠️ NO THRESHOLD
9.00📈 view plot
⚠️ NO THRESHOLD
296.00📈 view plot
⚠️ NO THRESHOLD
5.76 x 1e3
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended()📈 view plot
⚠️ NO THRESHOLD
149.69 x 1e3📈 view plot
⚠️ NO THRESHOLD
87,956.00📈 view plot
⚠️ NO THRESHOLD
123.22 x 1e3📈 view plot
⚠️ NO THRESHOLD
113.00📈 view plot
⚠️ NO THRESHOLD
740.00📈 view plot
⚠️ NO THRESHOLD
124.07 x 1e3
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple()📈 view plot
⚠️ NO THRESHOLD
130.25 x 1e3📈 view plot
⚠️ NO THRESHOLD
79,997.00📈 view plot
⚠️ NO THRESHOLD
111.79 x 1e3📈 view plot
⚠️ NO THRESHOLD
37.00📈 view plot
⚠️ NO THRESHOLD
522.00📈 view plot
⚠️ NO THRESHOLD
112.35 x 1e3
reporting_token_benchmark::key_derivation_group::bench_key_derivation📈 view plot
⚠️ NO THRESHOLD
77.03 x 1e3📈 view plot
⚠️ NO THRESHOLD
51,035.00📈 view plot
⚠️ NO THRESHOLD
71.06 x 1e3📈 view plot
⚠️ NO THRESHOLD
25.00📈 view plot
⚠️ NO THRESHOLD
167.00📈 view plot
⚠️ NO THRESHOLD
71.25 x 1e3
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message()📈 view plot
⚠️ NO THRESHOLD
24.46 x 1e3📈 view plot
⚠️ NO THRESHOLD
5,719.00📈 view plot
⚠️ NO THRESHOLD
8.38 x 1e3📈 view plot
⚠️ NO THRESHOLD
31.00📈 view plot
⚠️ NO THRESHOLD
455.00📈 view plot
⚠️ NO THRESHOLD
8.87 x 1e3
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message()📈 view plot
⚠️ NO THRESHOLD
11.89 x 1e3📈 view plot
⚠️ NO THRESHOLD
2,102.00📈 view plot
⚠️ NO THRESHOLD
3.10 x 1e3📈 view plot
⚠️ NO THRESHOLD
2.00📈 view plot
⚠️ NO THRESHOLD
251.00📈 view plot
⚠️ NO THRESHOLD
3.35 x 1e3
reporting_token_benchmark::token_calculation_group::bench_token_calculation📈 view plot
⚠️ NO THRESHOLD
34.83 x 1e3📈 view plot
⚠️ NO THRESHOLD
21,863.00📈 view plot
⚠️ NO THRESHOLD
30.59 x 1e3📈 view plot
⚠️ NO THRESHOLD
14.00📈 view plot
⚠️ NO THRESHOLD
119.00📈 view plot
⚠️ NO THRESHOLD
30.72 x 1e3
🐰 View full continuous benchmarking report in Bencher

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/client/sender_keys.rs (1)

37-59: Good optimization: Pre-computing prefixes reduces allocations.

Moving the prefix string formatting outside the filter loop (lines 38-43) is a solid optimization that eliminates repeated allocations per participant. The filtering logic correctly handles all three cases: colon prefix, at-sign prefix, and exact match.

Optional: Minor simplification opportunity

The exact match checks at lines 53 and 58 (own_lid_user.as_ref().is_some_and(|lid| p.as_str() == lid)) are slightly redundant. If lid_prefixes is Some, then own_lid_user is also Some, so you could capture the user string in the prefix tuple to avoid the repeated lookup:

 let lid_prefixes = own_lid_user
     .as_ref()
-    .map(|lid| (format!("{lid}:"), format!("{lid}@")));
+    .map(|lid| (format!("{lid}:"), format!("{lid}@"), lid.as_str()));

Then in the filter:

-let is_own_lid = lid_prefixes.as_ref().is_some_and(|(colon, at)| {
-    p.starts_with(colon)
-        || p.starts_with(at)
-        || own_lid_user.as_ref().is_some_and(|lid| p.as_str() == lid)
+let is_own_lid = lid_prefixes.as_ref().is_some_and(|(colon, at, exact)| {
+    p.starts_with(colon) || p.starts_with(at) || p.as_str() == *exact
 });

However, this is a very minor improvement and the current code is clear and correct.

wacore/libsignal/src/protocol/session_cipher.rs (1)

494-557: Ownership-based session_state handling looks correct; consider a guard to future‑proof restore

The take_session_state + set_session_state pattern correctly restores the current session on all current paths (success, DuplicatedMessage, PreKey failure, Whisper fallback). This preserves behavior while avoiding a clone.

If this function evolves, a small RAII guard (that writes back on drop unless explicitly “committed”) would reduce the risk of a future early‑return forgetting to call set_session_state.

wacore/libsignal/src/protocol/state/session.rs (1)

647-678: New take/restore APIs are coherent; consider enforcing the archived session cap

take_session_state, take_previous_session, and restore_previous_session provide the right primitives for ownership‑based manipulation of current/previous sessions and integrate cleanly with SessionState/SessionStructure conversions.

One thing to watch: restore_previous_session does not enforce consts::ARCHIVED_STATES_MAX_LENGTH, unlike archive_current_state_inner. If these APIs are later used heavily, it would be safer either to enforce the same cap here (e.g. drop the oldest when exceeding the limit) or to document that callers must uphold the invariant themselves.

wacore/binary/src/node.rs (1)

2-8: ValueRef abstraction for attribute values is well‑structured; document the public API break

The introduction of ValueRef<'a> (String/Jid), its helpers (as_str, as_jid, to_jid, to_string_cow, Display), and the switch to AttrsRef<'a> = Vec<(Cow<'a, str>, ValueRef<'a>)> form a coherent model:

  • Owned Node → borrowed NodeRef now tags attrs as ValueRef::String without changing behavior.
  • NodeRef::get_attr and attrs_iter expose the richer value type, enabling callers to avoid JID string allocations when they care.
  • NodeRef::to_owned correctly round‑trips back to Node by using to_string_cow().into_owned() for each value.

Since AttrsRef and NodeRef::get_attr are public, this is a source‑breaking change for downstream crates. It would be good to call that out in the crate’s changelog / release notes so users know to update their code to handle ValueRef rather than plain strings.

Also applies to: 9-16, 18-51, 53-63, 119-135, 203-205, 207-209, 240-247

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0857447 and ec41ef4.

📒 Files selected for processing (16)
  • src/appstate_sync.rs
  • src/client.rs
  • src/client/device_registry.rs
  • src/client/sender_keys.rs
  • src/client/sessions.rs
  • src/lid_pn_cache.rs
  • src/message.rs
  • src/prekeys.rs
  • src/session.rs
  • wacore/binary/src/attrs.rs
  • wacore/binary/src/decoder.rs
  • wacore/binary/src/encoder.rs
  • wacore/binary/src/node.rs
  • wacore/libsignal/src/protocol/session_cipher.rs
  • wacore/libsignal/src/protocol/state/session.rs
  • wacore/tests/binary_protocol_test.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Never modify Device state directly; use DeviceCommand + PersistenceManager::process_command() for state changes
For read-only Device state access, use PersistenceManager::get_device_snapshot()
All blocking I/O (such as ureq calls) and heavy CPU-bound tasks (like media encryption) MUST be wrapped in tokio::task::spawn_blocking to avoid stalling the async runtime
Use Client::chat_locks to serialize per-chat operations in asynchronous code
Use thiserror for custom domain-specific errors (e.g., SocketError) and anyhow::Error for functions with multiple failure modes
Avoid .unwrap() and .expect() outside of tests and unrecoverable logic paths
Use the Downloadable trait in wacore/src/download.rs for implementing generic media download interfaces across message types
Always refresh MediaConn if it's expired before using it to get current media servers and auth tokens
Run cargo fmt before finalizing a feature or fix
Run cargo clippy --all-targets before finalizing a feature or fix
Run cargo test --all before finalizing a feature or fix

Files:

  • src/session.rs
  • src/prekeys.rs
  • src/lid_pn_cache.rs
  • src/client/device_registry.rs
  • src/client/sessions.rs
  • src/client/sender_keys.rs
  • wacore/libsignal/src/protocol/state/session.rs
  • wacore/tests/binary_protocol_test.rs
  • src/appstate_sync.rs
  • wacore/libsignal/src/protocol/session_cipher.rs
  • src/client.rs
  • wacore/binary/src/decoder.rs
  • wacore/binary/src/node.rs
  • src/message.rs
  • wacore/binary/src/attrs.rs
  • wacore/binary/src/encoder.rs
wacore/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

wacore crate must remain platform-agnostic and have no dependencies on Tokio or specific databases

Files:

  • wacore/libsignal/src/protocol/state/session.rs
  • wacore/tests/binary_protocol_test.rs
  • wacore/libsignal/src/protocol/session_cipher.rs
  • wacore/binary/src/decoder.rs
  • wacore/binary/src/node.rs
  • wacore/binary/src/attrs.rs
  • wacore/binary/src/encoder.rs
🧠 Learnings (2)
📚 Learning: 2025-12-25T03:36:19.692Z
Learnt from: CR
Repo: jlucaso1/whatsapp-rust PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T03:36:19.692Z
Learning: Applies to **/*.rs : Avoid `.unwrap()` and `.expect()` outside of tests and unrecoverable logic paths

Applied to files:

  • wacore/tests/binary_protocol_test.rs
📚 Learning: 2025-12-25T03:36:19.692Z
Learnt from: CR
Repo: jlucaso1/whatsapp-rust PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T03:36:19.692Z
Learning: Applies to wacore/**/*.rs : `wacore` crate must remain platform-agnostic and have no dependencies on Tokio or specific databases

Applied to files:

  • wacore/tests/binary_protocol_test.rs
🧬 Code graph analysis (6)
src/client/sessions.rs (1)
wacore/src/usync.rs (1)
  • jids (27-34)
src/client/sender_keys.rs (1)
wacore/binary/src/jid.rs (2)
  • lid (330-336)
  • pn (321-327)
wacore/tests/binary_protocol_test.rs (2)
wacore/binary/src/node.rs (2)
  • as_str (20-25)
  • get_attr (203-205)
wacore/src/request.rs (1)
  • as_str (16-21)
wacore/binary/src/decoder.rs (1)
wacore/binary/src/token.rs (2)
  • get_double_token (41-46)
  • get_single_token (37-39)
src/message.rs (2)
wacore/libsignal/src/protocol/protocol.rs (1)
  • ciphertext (456-458)
wacore/libsignal/src/protocol/group_cipher.rs (1)
  • group_decrypt (152-235)
wacore/binary/src/encoder.rs (2)
wacore/binary/src/node.rs (2)
  • attrs (145-147)
  • attrs (243-247)
wacore/binary/src/builder.rs (1)
  • attrs (24-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build & Test
  • GitHub Check: benchmark
🔇 Additional comments (11)
src/client.rs (1)

1229-1229: LGTM: Clean pre-allocation optimization.

Pre-allocating to_request with the exact capacity needed avoids reallocations during the subsequent push operations in the loop (lines 1232-1241).

wacore/tests/binary_protocol_test.rs (1)

85-86: LGTM: Test correctly adapted for ValueRef changes.

The change from .as_ref() to .as_str().expect(...) correctly handles the new ValueRef type that can represent either String or Jid values. The use of .expect() in test code is appropriate per coding guidelines.

Also applies to: 93-94

src/session.rs (1)

112-113: LGTM: Appropriate pre-allocation for session batching.

Pre-allocating both vectors with jids_needing_sessions.len() is a sensible upper bound since each JID will go into either to_process or to_wait. This avoids reallocations during the classification loop (lines 115-128).

src/prekeys.rs (1)

100-101: LGTM: Precise pre-allocation for pre-key generation.

Pre-allocating with WANTED_PRE_KEY_COUNT matches the loop at line 133 that generates up to this many keys. The early break at line 142 for ID overflow is a rare edge case, so the capacity hint is appropriate for the common path.

src/client/device_registry.rs (1)

148-156: LGTM: Efficient cloning strategy with clear intent.

The change from assignment to clone_from (line 148) allows reusing the existing allocation in record.user, and creating an explicit record_for_cache (line 151) makes the ownership flow clearer: the cache gets a clone while the backend consumes the original record (line 160). This reduces allocations compared to cloning for both destinations.

src/lid_pn_cache.rs (1)

84-108: LGTM: Smart clone reduction from 2 to 1.

Computing should_update_pn upfront (lines 84-91) and cloning only once (line 94) is a nice optimization. When both maps need updating, this reduces clones from 2 to 1 by reusing the original entry for the PN map (lines 103-108) instead of cloning again.

src/appstate_sync.rs (1)

137-150: LGTM: Well-chosen capacity hints for batch processing.

Pre-allocating need_db_lookup with patch.mutations.len() (line 137) is a reasonable upper bound since the loop (lines 138-146) filters duplicates. Similarly, db_prev capacity of need_db_lookup.len() (line 150) is exact for the subsequent loop (lines 151-159) that populates it.

src/client/sessions.rs (1)

55-58: Slice‑based API and preallocation changes are sound

Using Vec::with_capacity(resolved_jids.len()), iterating chunks(...) as slices, and changing fetch_and_establish_sessions to take &[Jid] together remove redundant allocations without changing behavior. Callers (ensure_e2e_sessions, establish_primary_phone_session_immediate) are updated correctly.

Also applies to: 83-89, 96-162, 194-197

src/message.rs (1)

380-382: Borrowing ciphertext slices instead of allocating Vecs is correct here

Switching to &[u8] for ciphertext in both session and group batch paths, and passing those slices directly into PreKeySignalMessage::try_from, SignalMessage::try_from, message_decrypt, and group_decrypt removes unnecessary clones while keeping lifetimes safe (the backing Vec<u8> lives as long as the Node). The preallocation for session_enc_nodes / group_content_enc_nodes is also harmless and aligns with the PR goal.

Also applies to: 549-576, 560-590, 661-671, 849-852, 873-876

wacore/binary/src/decoder.rs (1)

3-4: ValueRef‑based attribute decoding matches prior semantics and avoids JID string allocs

read_value correctly mirrors read_value_as_string’s token mapping while returning ValueRef (string vs JID) instead of eagerly allocating a full JID string. read_attributes now uses this and falls back to ValueRef::String(Cow::Borrowed(\"\")) for LIST_EMPTY, which matches the previous “missing value → empty string” behavior.

The lifetime wiring (ValueRef<'a> holding JidRef<'a> or Cow<'a, str>) is consistent with Decoder<'a> and NodeRef<'a>, so zero‑copy behavior is preserved.

Also applies to: 204-240, 327-337

wacore/binary/src/encoder.rs (1)

322-331: write_attributes_ref correctly bridges ValueRef to the existing string encoder

Using value.to_string_cow() here is the right pairing with the new ValueRef representation on the decode side: it preserves prior behavior for string attributes and ensures JID values are serialized via their string form so write_string can re‑encode them using the optimized JID tokens.

Comment on lines +6 to 7
use crate::node::{Attrs, Node, NodeRef, ValueRef};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Get file size and content of attrs.rs
wc -l wacore/binary/src/attrs.rs

Repository: jlucaso1/whatsapp-rust

Length of output: 96


🏁 Script executed:

# Read the attrs.rs file to understand the structure
cat -n wacore/binary/src/attrs.rs | head -200

Repository: jlucaso1/whatsapp-rust

Length of output: 7305


🏁 Script executed:

# Search for ValueRef definition and AttrParserRef implementation
fd -t f "*.rs" wacore/ | xargs rg -l "ValueRef|AttrParserRef" | head -10

Repository: jlucaso1/whatsapp-rust

Length of output: 502


🏁 Script executed:

# Search for optional_string usages across the codebase
rg -n "optional_string\(" --type rust

Repository: jlucaso1/whatsapp-rust

Length of output: 4112


🏁 Script executed:

# Find ValueRef definition and key methods
cat -n wacore/binary/src/node.rs | grep -A 30 "enum ValueRef\|impl.*ValueRef"

Repository: jlucaso1/whatsapp-rust

Length of output: 3857


🏁 Script executed:

# Search for as_str and to_jid implementations
rg -n "fn as_str|fn to_jid" wacore/binary/src/node.rs -A 5

Repository: jlucaso1/whatsapp-rust

Length of output: 473


🏁 Script executed:

# Check the specific call sites mentioned as potentially problematic
rg -B 3 -A 3 'optional_string\("jid"\)' wacore/src/features/contacts.rs

Repository: jlucaso1/whatsapp-rust

Length of output: 137


🏁 Script executed:

# Find contacts.rs in the correct location
find . -name "contacts.rs" -type f

Repository: jlucaso1/whatsapp-rust

Length of output: 92


🏁 Script executed:

# Search for optional_string("jid") calls more broadly
rg -n 'optional_string\("jid"\)' --type rust

Repository: jlucaso1/whatsapp-rust

Length of output: 455


🏁 Script executed:

# Check what the encoder does to understand how attributes become ValueRef variants
rg -n "ValueRef::" wacore/binary/src/encoder.rs -B 2 -A 2

Repository: jlucaso1/whatsapp-rust

Length of output: 48


🏁 Script executed:

# Look for how attributes are decoded to understand if "jid" attributes are decoded as Jid or String variants
rg -n "jid.*=|ValueRef" wacore/binary/src/decoder.rs | head -40

Repository: jlucaso1/whatsapp-rust

Length of output: 1165


🏁 Script executed:

# Search for how jid attributes are encoded/created
rg -n '"jid"' wacore/binary/src/ --type rust -B 2 -A 2

Repository: jlucaso1/whatsapp-rust

Length of output: 48


🏁 Script executed:

# Check the actual call sites in contacts.rs to see how jid_str is used
rg -B 5 -A 10 'optional_string\("jid"\)' src/features/contacts.rs

Repository: jlucaso1/whatsapp-rust

Length of output: 2053


🏁 Script executed:

# See if there's any code that explicitly creates ValueRef::Jid for jid attributes
rg -n 'attrs.*jid' wacore/binary/src/ --type rust -i

Repository: jlucaso1/whatsapp-rust

Length of output: 48


🏁 Script executed:

# Look for attributes that might be JID-typed in the decoder logic
rg -n "JID_PAIR|AD_JID|INTEROP_JID|FB_JID" wacore/binary/src/decoder.rs -B 5 -A 5

Repository: jlucaso1/whatsapp-rust

Length of output: 2461


🏁 Script executed:

# Check if there's any encoder logic that creates JID-typed attributes
rg -n "ValueRef::Jid" wacore/binary/src/encoder.rs -B 3 -A 3

Repository: jlucaso1/whatsapp-rust

Length of output: 48


🏁 Script executed:

# Search for all attributes being read to understand which ones might be JID-typed
rg -n 'attrs\(\)\.optional_' src/ --type rust | head -50

Repository: jlucaso1/whatsapp-rust

Length of output: 3497


🏁 Script executed:

# Look at the test to understand how attributes are structured
cat -n wacore/tests/binary_protocol_test.rs | head -100

Repository: jlucaso1/whatsapp-rust

Length of output: 4435


🏁 Script executed:

# Check if there's any documentation or protocol spec about which attributes are JID-typed
find . -name "*.md" -type f | xargs grep -l "jid\|attribute" 2>/dev/null | head -5

Repository: jlucaso1/whatsapp-rust

Length of output: 77


🏁 Script executed:

# Search for how attributes are created during decoding to see if any are JID-typed
rg -n "read_attribute\|read_attr" wacore/binary/src/decoder.rs -A 10

Repository: jlucaso1/whatsapp-rust

Length of output: 48


🏁 Script executed:

# Check if there are any integration tests that show JID-typed attributes being used
rg -n "JID_PAIR\|AD_JID" wacore/ --type rust

Repository: jlucaso1/whatsapp-rust

Length of output: 48


🏁 Script executed:

# Look at the actual protocol to understand which attributes might be JID-typed
rg -n 'token::' wacore/binary/src/decoder.rs | grep -i "attr\|jid" | head -20

Repository: jlucaso1/whatsapp-rust

Length of output: 755


🏁 Script executed:

# Check the encoder to see if it explicitly encodes jid attributes as strings or JID tokens
rg -n "jid\|Jid" wacore/binary/src/encoder.rs -B 2 -A 2 | head -60

Repository: jlucaso1/whatsapp-rust

Length of output: 48


🏁 Script executed:

# Look at how attributes are built in the builder to understand the protocol
cat -n wacore/binary/src/builder.rs | head -100

Repository: jlucaso1/whatsapp-rust

Length of output: 2091


🏁 Script executed:

# Check if the encoder has any logic to encode attributes as JID types
cat -n wacore/binary/src/encoder.rs

Repository: jlucaso1/whatsapp-rust

Length of output: 24604


Use optional_jid() for "jid" attributes; optional_string() now returns None for JID-typed attribute values

The migration of AttrParserRef to ValueRef introduced a behavior change in optional_string(): it now returns None for attributes encoded as JID tokens (JID_PAIR, AD_JID, etc.), whereas previously they would have been materialized as strings like "user@server".

This affects several call sites that read the "jid" attribute:

  • src/features/contacts.rs (lines 261, 290, 398)
  • src/features/blocking.rs (line 74)

These locations call optional_string("jid") and then attempt to parse the result as a Jid. If the "jid" attribute is encoded as a JID token in the protocol (which occurs when the encoder detects valid JID format during serialization), the call returns None instead of the string representation.

Update these call sites to use optional_jid("jid") instead, which correctly handles both String and Jid variants.

🤖 Prompt for AI Agents
In wacore/binary/src/attrs.rs around lines 6-7 and referenced call sites
src/features/contacts.rs (lines 261, 290, 398) and src/features/blocking.rs
(line 74), replace uses of optional_string("jid") with optional_jid("jid") so
JID-typed attributes encoded as JID tokens are correctly returned and parsed;
update the imports if necessary to bring optional_jid into scope and run tests
to ensure compilation and Jid parsing work as before.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/client/sender_keys.rs (1)

31-35: Consider using get_device_snapshot() for read-only device state access.

As per coding guidelines, read-only Device state access should use PersistenceManager::get_device_snapshot() instead of the get_device_arc().await + .read().await pattern. This would simplify the code and align with the established pattern.

🔎 Suggested refactor
-        let device_store = self.persistence_manager.get_device_arc().await;
-        let device_guard = device_store.read().await;
-        let own_lid_user = device_guard.lid.as_ref().map(|j| j.user.clone());
-        let own_pn_user = device_guard.pn.as_ref().map(|j| j.user.clone());
-        drop(device_guard);
+        let device_snapshot = self.persistence_manager.get_device_snapshot().await;
+        let own_lid_user = device_snapshot.lid.as_ref().map(|j| j.user.clone());
+        let own_pn_user = device_snapshot.pn.as_ref().map(|j| j.user.clone());

Based on coding guidelines for read-only Device state access.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec41ef4 and 7fbdef3.

📒 Files selected for processing (2)
  • src/client/sender_keys.rs
  • wacore/libsignal/src/protocol/state/session.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • wacore/libsignal/src/protocol/state/session.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Never modify Device state directly; use DeviceCommand + PersistenceManager::process_command() for state changes
For read-only Device state access, use PersistenceManager::get_device_snapshot()
All blocking I/O (such as ureq calls) and heavy CPU-bound tasks (like media encryption) MUST be wrapped in tokio::task::spawn_blocking to avoid stalling the async runtime
Use Client::chat_locks to serialize per-chat operations in asynchronous code
Use thiserror for custom domain-specific errors (e.g., SocketError) and anyhow::Error for functions with multiple failure modes
Avoid .unwrap() and .expect() outside of tests and unrecoverable logic paths
Use the Downloadable trait in wacore/src/download.rs for implementing generic media download interfaces across message types
Always refresh MediaConn if it's expired before using it to get current media servers and auth tokens
Run cargo fmt before finalizing a feature or fix
Run cargo clippy --all-targets before finalizing a feature or fix
Run cargo test --all before finalizing a feature or fix

Files:

  • src/client/sender_keys.rs
🧬 Code graph analysis (1)
src/client/sender_keys.rs (1)
wacore/binary/src/jid.rs (2)
  • lid (330-336)
  • pn (321-327)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build & Test
  • GitHub Check: benchmark
🔇 Additional comments (2)
src/client/sender_keys.rs (2)

37-44: Nice optimization to reduce allocations in the filter loop.

Pre-computing the prefix strings outside the loop is an effective optimization. Instead of allocating format strings for each participant (potentially many times), the code now allocates just once per user type (lid and pn). This aligns well with the PR's objective to reduce allocations.


51-56: Filter logic correctly handles various JID formats.

The filter logic appropriately checks for colon-prefixed (device-specific), at-sign-prefixed (full JID), and exact match (bare user) formats to identify and exclude own devices. The use of is_some_and is idiomatic and efficient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant