Skip to content

fix(iq): report <error> detail the IQ parser drops, once per process - #1202

Merged
jlucaso1 merged 2 commits into
mainfrom
fix/iq-error-dropped-detail-probe
Jul 30, 2026
Merged

fix(iq): report <error> detail the IQ parser drops, once per process#1202
jlucaso1 merged 2 commits into
mainfrom
fix/iq-error-dropped-detail-probe

Conversation

@jlucaso1

Copy link
Copy Markdown
Collaborator

Summary

RequestUtils::parse_iq_response reads code/text/type/backoff off the <error> node and drops the rest. Those four are exactly what WA Web's parseIqResponse keeps, so reading only them is the right default, but "we read what WA Web reads" and "the server sent nothing more" are different claims and only the first was ever checked: a bare bad-request gives no way to tell an empty error from a detailed one the parser discarded.

This adds a probe that names the unread parts and nothing else. It reports once per process, because the finding is a note to this library's maintainers rather than something a calling application can act on, and rejected IQs arrive in bursts (usync, prekey and app-state fan-outs all retry) where a per-occurrence warning would be noise at exactly the moment the log matters.

Protocol evidence

  • docs/captured-js/WA/Parse/IqResponse.js builds its error result from i.code, i.text, i.type and i.backoff only, confirming the four attributes we read are the full set WA Web keeps.
  • The same function takes a third argument for the rest: typeof n == "function" ? (l = n(e)) : (l = n.parseOrThrow(a)), where a is the <error> node. So WA Web does have an escape hatch for a call site that needs more, exposed as deprecatedSendIqErrorParser in docs/captured-js/WA/Deprecated/SendIq.js.
  • Worth stating plainly, since it sets the scope: no call site in the capture passes that third argument, and the only wrapper exposing it is a deprecated one. So there is no evidence yet that rejected IQs carry anything past those four attributes. This probe is what would produce that evidence.

Changes

  • dropped_error_detail names the attributes outside PARSED_ERROR_ATTRS, the child tags, and the kind of a raw payload. Names only, no values: an attribute value can hold a JID and the report lands at a level production enables. It reads content directly rather than children(), which answers None for a byte or string payload that is dropped just the same.
  • PARSED_ERROR_ATTRS mirrors what the parser reads, with the parser commented to point at it. Reading a fifth attribute without adding it there is what would make the probe report a false positive on every response.
  • The report is gated by a static AtomicBool and by log_enabled!, both checked before the scan: once the warning is out, or with warnings filtered off, every further rejected IQ costs one relaxed load. Racing callers settle it with a swap, so at most one message is emitted.
  • warn_on_dropped_error_detail is #[cold] to keep it out of parse_iq_response, which is on the receive path and has its code size gated in CI.
  • Drive-by: generate_request_id now uses the imported Ordering instead of the full path, which unused_qualifications requires once the import exists.

Cost

The happy path is untouched: nothing here runs for a successful IQ. On a rejected one the scan walks the error node's attributes and its content discriminant, and allocates nothing when there is nothing to report (a Filter yields a zero lower bound, so the collect does not allocate). The two Vecs are only ever materialized on the single occurrence that logs.

Not in this PR

If the probe does fire in production, the follow-up is to hand the detail to the caller rather than only to the log: log is a facade, so an application that installs no logger sees nothing, and one that does could already raise Client/Recv to debug. That means an IqError::ServerError field carrying the node, which is a public error-surface change touching every construction site, and it is not worth making speculatively before the probe shows there is anything to carry.

Validation

cargo fmt --all
cargo test -p wacore --lib          # 1335 passed
cargo clippy -p wacore --all-targets -- -D warnings

Full matrix left to CI.

parse_iq_response reads code/text/type/backoff off an <error> node, which is
exactly what WA Web's parseIqResponse keeps. What was never checked is whether
the server sends more than that: a bare bad-request gives no way to tell an
empty error apart from a detailed one the parser discarded.

This adds a probe that names the unread parts (attribute names, child tags, and
the kind of a raw payload) and nothing else, because a value can hold a JID and
the report lands at a level production enables.

It is a note to this library's maintainers rather than something a calling
application can act on, and rejected IQs arrive in bursts, so it fires once per
process. Both the once-flag and the log-level check run before the scan, so
every further rejected IQ costs one relaxed load; the scan itself allocates
nothing when there is nothing to report, and the function is #[cold] to keep it
out of the receive path's code size.

PARSED_ERROR_ATTRS mirrors what the parser reads, so reading a fifth attribute
without adding it there is what would make the probe report a false positive.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 657b9728-c9dd-4537-a8d6-e372f10bda56

📥 Commits

Reviewing files that changed from the base of the PR and between c06acb1 and fc4cfa3.

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

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of server error responses to capture additional diagnostic details.
    • Correctly detects error information in attributes, child content, and text or byte payloads.
    • Improved handling of empty responses and payloads for more consistent behavior.
  • Improvements

    • Added clearer, one-time warnings when responses contain unsupported or unparsed content.
    • Warning details are limited to safe, non-sensitive information.

Walkthrough

Changes

The IQ response parser detects unparsed error-node content and emits a redacted warning once per process. Tests cover parsed errors, attributes, child nodes, byte/text payloads, and empty payloads.

IQ error detail detection

Layer / File(s) Summary
Error parsing and warning detection
wacore/src/request.rs
IQ error handling extracts known attributes, inspects remaining attributes, child nodes, and nonempty payloads, then emits a warning once per process when details are dropped.
Error detail detection tests
wacore/src/request.rs
Tests validate detection of unparsed attributes, child nodes, byte/text payloads, and suppression for parsed or empty errors and empty payloads.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • oxidezap/whatsapp-rust#1100: Both changes modify IQ error handling in wacore/src/request.rs; that PR adds timeout and transport classification methods.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: reporting dropped IQ error details once per process.
Description check ✅ Passed The description directly explains the parser change, probe behavior, scope, performance considerations, tests, and validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/iq-error-dropped-detail-probe

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.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 9.95 MiB 9.95 MiB +2.28 KiB (+0.02%) 🔺
bin .text 7.97 MiB 7.98 MiB +2.00 KiB (+0.02%) 🔺
bin allocated (text+data+bss) 9.95 MiB 9.95 MiB +32 B (+0.00%) 🔺
llvm-lines wacore 511,608 512,919 +1,311 (+0.26%) 🔺
llvm-lines wacore copies 16,704 16,742 +38 (+0.23%) 🔺
llvm-lines whatsapp-rust lib 727,450 727,450 0
llvm-lines whatsapp-rust lib copies 22,955 22,955 0
deps crates (Cargo.lock) 462 462 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.80 MiB 1.80 MiB 0
.text wacore 687.43 KiB 689.71 KiB +2.28 KiB (+0.33%) 🔺
.text wacore_binary 91.42 KiB 91.42 KiB 0
.text wacore_libsignal 170.64 KiB 170.64 KiB 0
.text wacore_appstate 22.35 KiB 22.35 KiB 0
.text wacore_noise 21.79 KiB 21.79 KiB 0
.text waproto 1.74 MiB 1.74 MiB 0
.text whatsapp_rust_sqlite_storage 515.70 KiB 515.62 KiB -83 B (-0.02%) 🔽
.text whatsapp_rust_tokio_transport 40.49 KiB 40.49 KiB 0
.text whatsapp_rust_ureq_http_client 11.83 KiB 11.83 KiB 0
.text std 984.91 KiB 984.91 KiB 0
.text other deps 1.90 MiB 1.90 MiB -298 B (-0.01%) 🔽
Top movers (cargo-bloat attribution)
Crate main PR Δ
wacore 687.43 KiB 689.71 KiB +2.28 KiB (+0.33%)

Baseline: 9c3e96f84 (latest main run) · Head: 51ada0362 · Graphs

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wacore/src/request.rs`:
- Around line 549-561: The test a_fully_parsed_error_reports_nothing currently
relies on warn_on_dropped_error_detail reaching its warning branch, which
depends on global log::Level::Warn state. Update the test to assert the
warning-branch behavior directly through the relevant dropped-error-detail
result or branch-specific helper, while preserving the existing assertion that
fully parsed errors return None; do not rely on process-wide logger
configuration.
🪄 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 (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c12c3e1d-1932-4887-b897-0dbf4d4cd8e4

📥 Commits

Reviewing files that changed from the base of the PR and between 9c3e96f and c06acb1.

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

Comment thread wacore/src/request.rs
The call sat in the fully-parsed-error test claiming to cover the caller's
empty-detail return, but warn_on_dropped_error_detail exits at its log-level
guard when no logger is installed, which is the case in this test binary. So it
covered nothing, and with a logger installed it would instead flip the
process-wide once-flag other tests share. The pure function's result is what
the test can assert without depending on either.
@jlucaso1
jlucaso1 merged commit 13fce2f into main Jul 30, 2026
23 of 24 checks passed
@jlucaso1
jlucaso1 deleted the fix/iq-error-dropped-detail-probe branch July 30, 2026 23:48
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