Skip to content

chore: audit follow-ups (perf, cleanup, helpers) - #584

Merged
jlucaso1 merged 3 commits into
mainfrom
chore/audit-followups
Apr 23, 2026
Merged

chore: audit follow-ups (perf, cleanup, helpers)#584
jlucaso1 merged 3 commits into
mainfrom
chore/audit-followups

Conversation

@jlucaso1

Copy link
Copy Markdown
Collaborator

Summary

Follow-ups from a codebase audit pass. Bundled together because each
piece is small and reviewable on its own.

Perf: avoid String allocation for numeric and bool attrs

  • New From<u8..u128 / i8..i128 / usize / isize / bool> impls on
    NodeValue route integers through itoa + CompactString, keeping
    them inline on the stack instead of heap-allocating via
    .to_string() (max 20 chars fits the 24-byte inline capacity).
  • Refactored ~27 call sites across src/ and wacore/ to pass values
    directly to .attr(...).
  • ProtocolNode derive macro now emits the new pattern for U32/U64
    fields, so downstream generated code inherits the improvement.
  • New numeric_attr_benchmark (iai-callgrind) locks in the result.

Measured against the .to_string() → NodeValue path:

case baseline cycles new cycles reduction
u32 attr 1269 614 ~52%
u64 attr 1287 655 ~49%
i64 attr 1522 735 ~52%
100 u64 attrs in a loop 55995 23689 ~58%

RAM hits drop ~42% on individual attrs, consistent with removing the
heap path.

Dead code

  • Removed UnimplementedHandler in its entirety (struct, the file, and
    handle_unimplemented on Client). Nothing instantiated it anywhere
    in the crate.
  • Removed the unused runtime field from NoiseSocket. It was tagged
    #[allow(dead_code)] with a kept for potential future spawns note
    but never read; the field's Arc<dyn Runtime> was already being
    cloned into the sender task locally.

Refactor and docs

  • Moved extract_content_bytes and extract_content_uint from
    wacore/src/iq/prekeys.rs into wacore/src/iq/node.rs, where the
    other node helpers (required_child, optional_attr, etc.) already
    live, and dropped the local copies from prekeys.rs.
  • Added a require_from_jid! macro under src/handlers/macros.rs and
    applied it to four notification handlers (handle_identity_change,
    account_sync_devices, handle_picture_notification,
    handle_status_notification) that shared the exact same
    match-and-warn boilerplate. Other call sites with different semantics
    (optional JID, different return type, extra normalization) were left
    alone.
  • Rewrote the ordering comment in complete_offline_sync so it
    describes what the code actually does: readers observing
    offline_sync_completed = true short-circuit without touching the
    semaphore, any in-flight worker keeps draining on its old 1-permit
    Arc, and newly-spawned workers pick up the 64-permit semaphore via
    read_message_semaphore().

Test plan

  • cargo fmt --all
  • cargo build --workspace
  • cargo clippy --workspace --all-targets --exclude e2e-tests (no warnings)
  • cargo test --workspace --exclude e2e-tests (1249 passing, 0 failed)
  • cargo bench -p wacore-binary --bench numeric_attr_benchmark (captured the numbers above)

Outcome of a codebase audit pass. All validated with cargo fmt, clippy
--workspace --all-targets, and the full test suite (1249 tests passing,
excluding e2e).

Perf: avoid String allocation for numeric and bool attrs

Add From<integer> and From<bool> impls for NodeValue, routing integers
through itoa + CompactString so they inline on the stack instead of
heap-allocating via .to_string(). Per-attr cost drops ~50%, a loop of
100 attrs drops ~58%, RAM hits drop ~42% (iai-callgrind, see the new
numeric_attr_benchmark).

Refactored ~27 call sites across src/ and wacore/ to pass numerics and
bools directly to .attr(...). Updated the ProtocolNode derive macro to
emit the new pattern for U32/U64 fields.

Dead code

- Remove UnimplementedHandler (struct, its file, and handle_unimplemented
  in client). Nothing ever instantiated it.
- Remove unused runtime field from NoiseSocket (was tagged
  #[allow(dead_code)] with a "kept for potential future spawns" note).

Refactor and docs

- Move extract_content_bytes and extract_content_uint from
  wacore/src/iq/prekeys.rs into wacore/src/iq/node.rs, where the other
  node helpers already live.
- Add a require_from_jid! macro under src/handlers/macros.rs and apply
  it to four uniform notification handlers that shared the same
  match-and-warn pattern.
- Rewrite the complete_offline_sync ordering comment so it matches the
  actual code: readers observing the flag short-circuit without touching
  the semaphore, old workers drain on their old Arc, new workers pick
  up the 64-permit semaphore.
@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: abeb4048-abda-4a0c-af02-e0f96e1a9901

📥 Commits

Reviewing files that changed from the base of the PR and between f0cd55d and 67b04b8.

📒 Files selected for processing (1)
  • wacore/src/iq/groups.rs

📝 Walkthrough

Summary by CodeRabbit

  • Refactor

    • Standardized numeric/boolean attribute serialization across messages to reduce allocations and improve performance.
    • Consolidated common notification validation to reduce repetition and simplify handlers.
    • Removed unused unhandled-stanza handler and extracted shared helpers for payload extraction to ensure consistent behavior.
  • Tests

    • Added benchmarks measuring numeric attribute serialization performance.

Walkthrough

This PR centralizes numeric/boolean XML attribute handling by adding From impls for primitive types into NodeValue and updating builders to pass raw numeric/bool values to NodeBuilder::attr(). It also adds a require_from_jid! macro, removes the UnimplementedHandler and Client::handle_unimplemented, and adds related benchmarks and helpers.

Changes

Cohort / File(s) Summary
NodeValue Core Infrastructure
wacore/binary/src/node.rs
Added From impls for integers and bool to produce NodeValue::String (uses itoa::Buffer / "true"/"false").
Derive & Bench
wacore/derive/src/lib.rs, wacore/binary/Cargo.toml, wacore/binary/benches/numeric_attr_benchmark.rs
ProtocolNode derive emits numeric attrs directly; added numeric_attr_benchmark and benchmark file comparing stringification vs. itoa+CompactString.
IQ / Stanza Builders
wacore/src/iq/... (groups, mediaconn, dirty, props, tctoken, prekeys), wacore/src/send.rs, src/retry.rs, wacore/src/stanza/devices.rs, wacore/src/media_retry.rs, wacore/src/pair.rs
Replaced .to_string() attribute construction with raw numeric/bool attribute values across many IQ/stanza builders and tests.
Node Content Helpers
wacore/src/iq/node.rs, wacore/src/iq/prekeys.rs
Added extract_content_bytes and extract_content_uint (pub(crate)); removed duplicate local implementations and updated prekeys to use them.
Handler Macro & Removal
src/handlers/macros.rs, src/handlers/mod.rs, src/handlers/notification.rs, src/handlers/unimplemented.rs
Added #[macro_export] require_from_jid! and replaced repetitive from parsing in notification handlers; removed unimplemented handler and its registration.
Client & Sessions
src/client.rs, src/client/sessions.rs
Removed pub(crate) async fn handle_unimplemented(&self, tag: &str); clarified comments around offline sync concurrency.
Socket & Tests
src/socket/noise_socket.rs, src/unified_session.rs, various tests (wacore/src/usync.rs, others)
Removed runtime field from NoiseSocket; updated tests and helpers to pass numeric t/id values directly and simplified builders.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Ari4ka

—This needs to work correctly; verify serialized attribute outputs and handler behavior.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: performance improvements, cleanup of dead code, and helper refactoring from an audit pass.
Description check ✅ Passed The description provides detailed context for all major changes including performance metrics, dead code removal, refactoring details, and comprehensive test results.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/audit-followups

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37ce56bd41

ℹ️ 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".

Comment thread wacore/src/iq/groups.rs
.attr("expiration", self.expiration.to_string())
.attr("admin", self.admin_jid.to_string())
.attr("expiration", self.expiration)
.attr("admin", &self.admin_jid)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Serialize invite admin JID as string

Passing admin_jid directly here changes encoding from string-path JID serialization to NodeValue::Jid, which calls write_jid_owned and hard-fails when jid.device > 255 (it does a u8::try_from). The previous to_string() path did not error on those values and still produced a wire value, so this introduces a new runtime failure mode for AcceptGroupInviteV4Iq when invites carry high device IDs.

Useful? React with 👍 / 👎.

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wacore/src/iq/groups.rs`:
- Around line 2247-2251: Add a regression test that exercises the
NodeBuilder::new("accept") path (the block that calls .attr("code", &self.code),
.attr("expiration", self.expiration), .attr("admin", &self.admin_jid) and
.build()) and assert the built node contains the exact serialized attribute
values for "expiration" and "admin"; specifically, create a deterministic invite
with a known i64 expiration and a known Jid, build the node via the same code
path, and assert node.attr("expiration") equals the expected decimal string and
node.attr("admin") equals the expected Jid string to lock down the trait
conversions for i64 and &Jid.
🪄 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: a7c55080-3c17-40a8-b30f-d6897ea45b5b

📥 Commits

Reviewing files that changed from the base of the PR and between b112362 and 37ce56b.

📒 Files selected for processing (27)
  • src/client.rs
  • src/client/sessions.rs
  • src/features/newsletter.rs
  • src/handlers/macros.rs
  • src/handlers/mod.rs
  • src/handlers/notification.rs
  • src/handlers/unimplemented.rs
  • src/retry.rs
  • src/socket/noise_socket.rs
  • src/unified_session.rs
  • wacore/binary/Cargo.toml
  • wacore/binary/benches/numeric_attr_benchmark.rs
  • wacore/binary/src/node.rs
  • wacore/derive/src/lib.rs
  • wacore/src/iq/dirty.rs
  • wacore/src/iq/groups.rs
  • wacore/src/iq/mediaconn.rs
  • wacore/src/iq/node.rs
  • wacore/src/iq/prekeys.rs
  • wacore/src/iq/privacy.rs
  • wacore/src/iq/props.rs
  • wacore/src/iq/tctoken.rs
  • wacore/src/media_retry.rs
  • wacore/src/pair.rs
  • wacore/src/send.rs
  • wacore/src/stanza/devices.rs
  • wacore/src/usync.rs
💤 Files with no reviewable changes (2)
  • src/socket/noise_socket.rs
  • src/handlers/unimplemented.rs

Comment thread wacore/src/iq/groups.rs
@github-actions

github-actions Bot commented Apr 23, 2026

Copy link
Copy Markdown

Benchmark Results

59 unchanged benchmark(s)
Benchmark Current Baseline Change
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message() 3,933 3,933 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 12,038 12,038 +0.0%
reporting_token_benchmark::key_derivation_group::bench_key_derivation 43,514 43,514 +0.0%
reporting_token_benchmark::token_calculation_group::bench_token_calculation 19,365 19,365 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple() 68,579 68,579 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 76,679 76,679 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message() 2,230 2,230 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message() 5,988 5,988 +0.0%
send_receive_benchmark::dm_send::bench_dm_send text:setup_dm_send() 169,495 169,106 +0.2%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 190,986 190,838 +0.1%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 875,151 875,079 +0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 966,328 966,172 +0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 1,453,275 1,453,247 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 2,574,906 2,575,126 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 9,375,926 9,375,066 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 44,455,680 44,262,988 +0.4%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 12,576,396 12,683,564 -0.8%
binary_benchmark::marshal_group::bench_marshal_allocating 71,247 71,247 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 71,300 71,300 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 98,367 98,367 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 78,801 78,801 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 71,347 71,347 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 7,518 7,518 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 7,561 7,561 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 9,273 9,273 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 530,504 530,504 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 530,072 530,072 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 531,427 531,427 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 8,506,160 8,506,160 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 8,450,412 8,450,412 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 19,677,947 19,677,947 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled() 2,468 2,468 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled() 33,558 33,558 +0.0%
binary_benchmark::unpack_group::bench_unpack_uncompressed 787 787 +0.0%
binary_benchmark::unpack_group::bench_unpack_compressed 526,732 526,732 +0.0%
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled() 4,986 4,986 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled() 5,315 5,315 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled() 61,874 61,874 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled() 5,347 5,347 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled() 61,942 61,942 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled() 6,734 6,734 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled() 85,564 85,564 +0.0%
binary_benchmark::child_iteration_group::bench_get_children_by_tag 477,570 477,570 +0.0%
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled() 11,563 11,563 +0.0%
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users() 17,139,711 17,219,510 -0.5%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 157,113 157,113 +0.0%
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message() 5,510,200 5,510,200 +0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session() 157,827 157,827 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender() 296,767 296,699 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution() 706,282 706,282 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 12,543,046 12,683,805 -1.1%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 27,447,105 27,518,996 -0.3%
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message() 3,467,011 3,467,011 +0.0%
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message() 126,082,953 124,338,943 +1.4%
libsignal_benchmark::signature_group::bench_key_generation keygen 2,830,452 2,830,452 +0.0%
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions() 46,003 46,003 +0.0%
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages() 5,072,844 5,072,844 +0.0%
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session() 316,083 316,083 +0.0%
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction() 14,255,917 14,255,917 +0.0%
No significant changes detected.

Adds a regression test around the NodeBuilder::new("accept") path that
the perf refactor touched: builds an AcceptGroupInviteV4Iq with a known
i64 expiration and a known admin Jid, and asserts the serialized
attribute values.

This pins the behavior of the From<i64> and From<&Jid> trait
conversions on NodeValue so that any future change to them trips this
test first. The .attr("admin", &self.admin_jid) path is already
ubiquitous in this crate (devices.rs, prekeys.rs, privacy.rs,
business.rs, groups.rs participant/subgroup nodes) and stays consistent
here.

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wacore/src/iq/groups.rs`:
- Around line 3547-3552: The test currently checks admin using parsed
equivalence via accept.attrs().optional_jid("admin") == Some(admin_jid); tighten
it to assert the exact serialized wire string instead by calling
accept.attrs().optional_string("admin").as_deref() and compare to the expected
literal (the exact JID string you expect on the wire) so changes in formatting
will be detected; replace the optional_jid assertion with an optional_string
assertion referencing accept.attrs() and the expected wire string instead of
admin_jid.
🪄 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: 28de0bec-dbbd-4fa7-b0b7-944c2a50e223

📥 Commits

Reviewing files that changed from the base of the PR and between 37ce56b and f0cd55d.

📒 Files selected for processing (1)
  • wacore/src/iq/groups.rs

Comment thread wacore/src/iq/groups.rs
Tighten test_accept_group_invite_v4_iq_attrs to compare the "admin"
attribute via optional_string against the expected literal
"5511999887766@s.whatsapp.net" instead of parsing it back into a Jid
for a structural compare. optional_string on a NodeValue::Jid goes
through the Display impl (the same path the old .to_string() call used
to take), so this locks down formatting drift too.
@jlucaso1
jlucaso1 merged commit 8bf0d1f into main Apr 23, 2026
11 checks passed
@jlucaso1
jlucaso1 deleted the chore/audit-followups branch April 23, 2026 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant