Skip to content

fix: Parse device JID attribute in device notifications - #243

Merged
jlucaso1 merged 3 commits into
mainfrom
fix-device-notification-parser
Jan 25, 2026
Merged

fix: Parse device JID attribute in device notifications#243
jlucaso1 merged 3 commits into
mainfrom
fix-device-notification-parser

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jan 24, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Type-safe device notification parsing and a new public stanza module exposing device notification types and parsers.
  • Improvements

    • Device list updates now include LID-user context, per-device info (optional key index), key-index metadata and contact hash; notification types map to list update events.
    • Learning source tracking extended to include device notifications.
  • Chores

    • Cleaned assorted comment headers and test docs; added/expanded tests for parsing and notification flows.

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

@coderabbitai

coderabbitai Bot commented Jan 24, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a type-safe device notification parser under a new wacore::stanza module, extends event and LID–PN types to carry richer device notification data, updates the notification handler to use the new parser and persist LID–PN mappings, and removes several non-functional comment blocks across tests and benches.

Changes

Cohort / File(s) Summary
Stanza module & device parser
wacore/src/stanza/mod.rs, wacore/src/stanza/devices.rs
New stanza module and devices submodule implementing DeviceNotificationType, KeyIndexInfo, DeviceElement, DeviceOperation, DeviceNotification, parsing/serialization, lid→pn mapping detection, and extensive unit tests.
Types & events
wacore/src/types/events.rs, wacore/src/types/lid_pn.rs
Added DeviceNotificationInfo; expanded DeviceListUpdate to include lid_user, typed devices, key_index, contact_hash; added From<DeviceNotificationType> for DeviceListUpdateType; added LearningSource::DeviceNotification.
Handler & LID–PN cache
src/handlers/notification.rs, src/lid_pn_cache.rs
Replaced manual XML child parsing with DeviceNotification::try_parse; handle single operation per notification; persist LID–PN mappings via LearningSource::DeviceNotification; invalidate user device cache and dispatch DeviceListUpdate; tests updated and legacy helper removed.
Library exports
wacore/src/lib.rs
Exported new stanza module (pub mod stanza;).
Comment cleanup
src/client.rs, src/client/sessions.rs, src/message.rs, wacore/libsignal/benches/libsignal_benchmark.rs, wacore/libsignal/src/core/curve.rs, wacore/libsignal/src/protocol/state/session.rs, wacore/src/iq/dirty.rs
Removed non-functional block/header comments from tests and benchmarks; no behavioral changes.

Sequence Diagram(s)

sequenceDiagram
    participant Handler as Notification Handler
    participant Parser as DeviceNotification Parser
    participant EventSys as Event System
    participant Cache as LID-PN Cache

    Handler->>Parser: pass <notification type="devices"> node
    Parser->>Parser: try_parse() → DeviceNotification (from, lid_user, id, ts, operation)
    Parser-->>Handler: DeviceNotification
    Handler->>Handler: build DeviceListUpdate (update_type, devices, key_index?, contact_hash?)
    Handler->>Cache: persist lid→pn mapping (LearningSource::DeviceNotification) [if present]
    Handler->>EventSys: dispatch DeviceListUpdate(user, lid_user?, ...)
    EventSys->>Cache: invalidate user device cache
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I hopped through nodes with twitchy delight,

parsed devices by day and by night,
LID met PN and they learned to be friends,
old comments cleared — tidy code ascends,
nibble, hop, dispatch — the update sends!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title references parsing device JID attributes, but the changeset is a comprehensive refactor introducing type-safe device notification parsing with new public types and event structures—far beyond parsing a single JID attribute. Update the title to reflect the main objective, such as 'refactor: Introduce type-safe device notification parsing' or 'feat: Add DeviceNotification parser and related types'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

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 Jan 24, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchfix-device-notification-parser
Testbedubuntu-latest

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
binary_benchmark::unpack_group::bench_unpack_compressedInstructions
instructions x 1e3
📈 plot
🚷 threshold
🚨 alert (🔔)
556.53 x 1e3
(+13.15%)Baseline: 491.86 x 1e3
516.45 x 1e3
(107.76%)

Click to view all benchmark results
BenchmarkInstructionsBenchmark Result
instructions
(Result Δ%)
Upper Boundary
instructions
(Limit %)
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled()📈 view plot
🚷 view threshold
7,031.00
(-4.53%)Baseline: 7,364.70
7,732.93
(90.92%)
binary_benchmark::marshal_group::bench_marshal_allocating📈 view plot
🚷 view threshold
124,647.00
(-12.28%)Baseline: 142,093.86
149,198.55
(83.54%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer📈 view plot
🚷 view threshold
124,747.00
(-12.27%)Baseline: 142,194.73
149,304.47
(83.55%)
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled()📈 view plot
🚷 view threshold
106,274.00
(0.00%)Baseline: 106,274.00
111,587.70
(95.24%)
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled()📈 view plot
🚷 view threshold
8,487.00
(0.00%)Baseline: 8,487.00
8,911.35
(95.24%)
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled()📈 view plot
🚷 view threshold
49,338.00
(-0.38%)Baseline: 49,526.23
52,002.54
(94.88%)
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled()📈 view plot
🚷 view threshold
3,025.00
(-5.64%)Baseline: 3,205.84
3,366.13
(89.87%)
binary_benchmark::unpack_group::bench_unpack_compressed📈 view plot
🚷 view threshold
🚨 view alert (🔔)
556,531.00
(+13.15%)Baseline: 491,857.77
516,450.66
(107.76%)

binary_benchmark::unpack_group::bench_unpack_uncompressed📈 view plot
🚷 view threshold
779.00
(+0.02%)Baseline: 778.88
817.82
(95.25%)
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data()📈 view plot
🚷 view threshold
27,581,029.00
(-0.64%)Baseline: 27,759,311.32
29,147,276.89
(94.63%)
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message()📈 view plot
🚷 view threshold
5,571,063.00
(+0.02%)Baseline: 5,569,935.40
5,848,432.17
(95.26%)
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session()📈 view plot
🚷 view threshold
178,206.00
(+0.30%)Baseline: 177,677.24
186,561.10
(95.52%)
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session()📈 view plot
🚷 view threshold
179,022.00
(+0.30%)Baseline: 178,485.64
187,409.92
(95.52%)
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users()📈 view plot
🚷 view threshold
17,488,539.00
(+1.31%)Baseline: 17,262,883.44
18,126,027.61
(96.48%)
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender()📈 view plot
🚷 view threshold
295,925.00
(+0.09%)Baseline: 295,661.80
310,444.89
(95.32%)
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message()📈 view plot
🚷 view threshold
12,730,587.00
(+1.02%)Baseline: 12,602,094.44
13,232,199.16
(96.21%)
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution()📈 view plot
🚷 view threshold
716,092.00
(+0.02%)Baseline: 715,972.32
751,770.94
(95.25%)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()📈 view plot
🚷 view threshold
41,798.00
(+0.36%)Baseline: 41,647.33
43,729.70
(95.58%)
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages()📈 view plot
🚷 view threshold
5,530,955.00
(-0.17%)Baseline: 5,540,156.50
5,817,164.33
(95.08%)
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session()📈 view plot
🚷 view threshold
965,435.00
(-0.50%)Baseline: 970,268.83
1,018,782.28
(94.76%)
libsignal_benchmark::signature_group::bench_key_generation keygen📈 view plot
🚷 view threshold
2,823,499.00
(+0.02%)Baseline: 2,823,006.71
2,964,157.05
(95.25%)
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message()📈 view plot
🚷 view threshold
3,445,806.00
(-17.47%)Baseline: 4,175,111.71
4,383,867.30
(78.60%)
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message()📈 view plot
🚷 view threshold
125,659,244.00
(+0.66%)Baseline: 124,834,018.00
131,075,718.90
(95.87%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message()📈 view plot
🚷 view threshold
11,815.00
(+0.03%)Baseline: 11,811.41
12,401.98
(95.27%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message()📈 view plot
🚷 view threshold
3,837.00
(+0.17%)Baseline: 3,830.55
4,022.08
(95.40%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended()📈 view plot
🚷 view threshold
88,029.00
(+0.02%)Baseline: 88,007.52
92,407.89
(95.26%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple()📈 view plot
🚷 view threshold
80,074.00
(+0.03%)Baseline: 80,049.66
84,052.14
(95.27%)
reporting_token_benchmark::key_derivation_group::bench_key_derivation📈 view plot
🚷 view threshold
51,035.00
(0.00%)Baseline: 51,035.00
53,586.75
(95.24%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message()📈 view plot
🚷 view threshold
5,734.00
(+0.06%)Baseline: 5,730.41
6,016.93
(95.30%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message()📈 view plot
🚷 view threshold
2,121.00
(+0.30%)Baseline: 2,114.55
2,220.28
(95.53%)
reporting_token_benchmark::token_calculation_group::bench_token_calculation📈 view plot
🚷 view threshold
21,921.00
(+0.07%)Baseline: 21,905.46
23,000.74
(95.31%)
🐰 View full continuous benchmarking report in Bencher

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes the parsing of device notifications in the WhatsApp protocol to use the jid attribute (which contains the full JID including device ID) instead of a simple id attribute. It also adds support for learning LID-PN mappings from device notifications.

Changes:

  • Adds a new wacore::stanza::devices module with type-safe parsing for device notification stanzas
  • Updates device notification handling to extract device IDs from JID attributes and learn LID-PN mappings when available
  • Adds DeviceNotification as a new LearningSource for LID-PN cache mappings

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
wacore/src/stanza/mod.rs New module declaration for stanza types
wacore/src/stanza/devices.rs Type-safe parser for device notification stanzas with JID-based device elements
wacore/src/types/lid_pn.rs Adds DeviceNotification variant to LearningSource enum
wacore/src/types/events.rs Updates DeviceListUpdate structure with more detailed device info and LID support (breaking change)
wacore/src/lib.rs Registers new stanza module
src/handlers/notification.rs Refactors device notification handling to use new parser and learn LID-PN mappings
src/lid_pn_cache.rs Updates tests to include new learning source variant
wacore/src/iq/dirty.rs Removes verbose module-level documentation
Other files Removes section divider comments from tests and benchmarks

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread wacore/src/stanza/devices.rs Outdated
Comment on lines +63 to +67
let timestamp = node
.attrs()
.optional_u64("ts")
.ok_or_else(|| anyhow!("key-index-list missing required 'ts' attribute"))?
as i64;

Copilot AI Jan 24, 2026

Copy link

Choose a reason for hiding this comment

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

Casting u64 to i64 can cause overflow for timestamps greater than i64::MAX (9223372036854775807, which is year 2262). While this is unlikely for WhatsApp timestamps in practice, consider using a checked conversion or explicitly handling the overflow case to make this safer. Alternatively, you could keep timestamp as u64 if the protocol doesn't require signed values.

Suggested change
let timestamp = node
.attrs()
.optional_u64("ts")
.ok_or_else(|| anyhow!("key-index-list missing required 'ts' attribute"))?
as i64;
let timestamp_u64 = node
.attrs()
.optional_u64("ts")
.ok_or_else(|| anyhow!("key-index-list missing required 'ts' attribute"))?;
let timestamp = i64::try_from(timestamp_u64)
.map_err(|_| anyhow!("key-index-list 'ts' attribute value {} exceeds i64::MAX", timestamp_u64))?;

Copilot uses AI. Check for mistakes.
Comment thread wacore/src/stanza/devices.rs Outdated
let stanza_id = optional_attr(node, "id")
.map(String::from)
.unwrap_or_default();
let timestamp = node.attrs().optional_u64("t").unwrap_or(0) as i64;

Copilot AI Jan 24, 2026

Copy link

Choose a reason for hiding this comment

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

Casting u64 to i64 can cause overflow for timestamps greater than i64::MAX. While unlikely for WhatsApp timestamps in practice, consider using a checked conversion or explicitly handling the overflow case. Alternatively, keep timestamp as u64 if the protocol doesn't require signed values.

Suggested change
let timestamp = node.attrs().optional_u64("t").unwrap_or(0) as i64;
let timestamp = match node.attrs().optional_u64("t") {
Some(t) => {
if t > i64::MAX as u64 {
return Err(anyhow!("notification timestamp out of range: {}", t));
}
t as i64
}
None => 0,
};

Copilot uses AI. Check for mistakes.
Comment thread wacore/src/stanza/devices.rs Outdated
.attrs()
.optional_jid("jid")
.ok_or_else(|| anyhow!("device missing required 'jid' attribute"))?;
let key_index = node.attrs().optional_u64("key-index").map(|v| v as u32);

Copilot AI Jan 24, 2026

Copy link

Choose a reason for hiding this comment

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

Casting u64 to u32 can cause truncation for key-index values greater than u32::MAX. If the WhatsApp protocol guarantees key-index values fit in u32, consider using a checked conversion like try_into() to catch any protocol violations. Otherwise, document why the truncation is acceptable.

Suggested change
let key_index = node.attrs().optional_u64("key-index").map(|v| v as u32);
let key_index = match node.attrs().optional_u64("key-index") {
Some(v) => Some(u32::try_from(v).map_err(|_| anyhow!("device 'key-index' out of range: {}", v))?),
None => None,
};

Copilot uses AI. Check for mistakes.
@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 25, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

2 participants