Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/scripts/bench-comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import re
import sys

THRESHOLD = 0.05 # 5%
THRESHOLD = 0.02 # 2%


def load_baseline(data_js_path: str, bench_name: str) -> dict[str, int]:
Expand Down Expand Up @@ -84,7 +84,9 @@ def main():
lines.append("## Benchmark Results\n")

if regressions:
lines.append(f"**{len(regressions)} regression(s)** detected (>{THRESHOLD*100:.0f}% threshold):\n")
lines.append(
f"**{len(regressions)} regression(s)** detected (>{THRESHOLD * 100:.0f}% threshold):\n"
)
lines.append("| Benchmark | Current | Baseline | Change |")
lines.append("|-----------|---------|----------|--------|")
for r in regressions:
Expand All @@ -104,7 +106,9 @@ def main():
lines.append("")

if unchanged:
lines.append(f"<details>\n<summary>{len(unchanged)} unchanged benchmark(s)</summary>\n")
lines.append(
f"<details>\n<summary>{len(unchanged)} unchanged benchmark(s)</summary>\n"
)
lines.append("| Benchmark | Current | Baseline | Change |")
lines.append("|-----------|---------|----------|--------|")
for r in unchanged:
Expand Down
2 changes: 1 addition & 1 deletion src/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1515,7 +1515,7 @@ impl Client {
jids: &[Jid],
) -> Vec<std::sync::Arc<async_lock::Mutex<()>>> {
let mut mutexes = Vec::with_capacity(jids.len());
let mut buf = String::with_capacity(64);
let mut buf = wacore::types::jid::make_address_buffer();
for jid in jids {
wacore::types::jid::write_protocol_address_to(jid, &mut buf);
mutexes.push(self.session_lock_for(&buf).await);
Expand Down
76 changes: 62 additions & 14 deletions wacore/libsignal/src/core/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,58 +276,106 @@ where
)]
pub struct DeviceId(u32);

impl DeviceId {
#[inline]
pub const fn new(id: u32) -> Self {
Self(id)
}
}

impl fmt::Display for DeviceId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

const fn digit_count(n: u32) -> usize {
if n == 0 {
return 1;
}
n.ilog10() as usize + 1
}

#[inline]
fn append_device_suffix(buf: &mut String, device_id: DeviceId) {
let id = u32::from(device_id);
if id == 0 {
buf.push_str(".0");
} else {
use std::fmt::Write;
write!(buf, ".{id}").unwrap();
}
}

/// Single-buffer protocol address. The buffer stores `"{name}.{device_id}"` and
/// `name_len` marks where the name ends, so `name()` and `as_str()` are both
/// zero-cost slices. One String instead of two — halves allocation count for
/// one-shot construction and eliminates the copy in `reset_with()`.
#[derive(Clone, Debug)]
pub struct ProtocolAddress {
name: String,
buf: String,
name_len: usize,
device_id: DeviceId,
/// Pre-computed `"{name}.{device_id}"` — avoids allocation on every `to_string()`.
display: String,
}

impl ProtocolAddress {
pub fn new(name: String, device_id: DeviceId) -> Self {
let display = format!("{name}.{device_id}");
ProtocolAddress {
name,
let name_len = name.len();
let mut buf = name;
append_device_suffix(&mut buf, device_id);
Self {
buf,
name_len,
device_id,
}
}

/// Pre-allocated empty address. Call `reset_with()` to fill.
pub fn with_capacity(capacity: usize, device_id: DeviceId) -> Self {
let suffix_len = 1 + digit_count(u32::from(device_id));
Self {
buf: String::with_capacity(capacity + suffix_len),
name_len: 0,
device_id,
display,
}
}

/// Write the name via closure, then append the device_id suffix.
/// Single write pass — no intermediate copy.
pub fn reset_with(&mut self, write_name: impl FnOnce(&mut String)) {
self.buf.clear();
write_name(&mut self.buf);
self.name_len = self.buf.len();
append_device_suffix(&mut self.buf, self.device_id);
}

#[inline]
pub fn name(&self) -> &str {
&self.name
&self.buf[..self.name_len]
}

#[inline]
pub fn device_id(&self) -> DeviceId {
self.device_id
}

/// Returns the cached `"name.device_id"` string without allocation.
#[inline]
pub fn as_str(&self) -> &str {
&self.display
&self.buf
}
}

impl PartialEq for ProtocolAddress {
fn eq(&self, other: &Self) -> bool {
self.display == other.display
self.buf == other.buf
}
}

impl Eq for ProtocolAddress {}

impl Hash for ProtocolAddress {
fn hash<H: Hasher>(&self, state: &mut H) {
self.display.hash(state);
self.buf.hash(state);
}
}

Expand All @@ -339,12 +387,12 @@ impl PartialOrd for ProtocolAddress {

impl Ord for ProtocolAddress {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.display.cmp(&other.display)
self.buf.cmp(&other.buf)
}
}

impl fmt::Display for ProtocolAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.display)
f.write_str(&self.buf)
}
}
30 changes: 15 additions & 15 deletions wacore/src/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ where
let mut jids_needing_prekeys = Vec::with_capacity(devices.len());
let mut had_406 = false;

let mut reusable_addr = crate::types::jid::make_reusable_protocol_address();

for device_jid in devices {
// WhatsApp Web's SignalAddress.toString() normalizes PN → LID before
// creating signal addresses. We do the same: check LID session FIRST.
Expand All @@ -374,11 +376,11 @@ where
{
// Construct the LID JID with the same device ID
let lid_jid = Jid::lid_device(lid_user, device_jid.device);
let lid_address = lid_jid.to_protocol_address();
lid_jid.reset_protocol_address(&mut reusable_addr);

if stores
.session_store
.load_session(&lid_address)
.load_session(&reusable_addr)
.await?
.is_some()
{
Expand All @@ -394,10 +396,10 @@ where
}

// Fall back to direct address lookup (for LID JIDs or PN without LID mapping)
let signal_address = device_jid.to_protocol_address();
device_jid.reset_protocol_address(&mut reusable_addr);
if stores
.session_store
.load_session(&signal_address)
.load_session(&reusable_addr)
.await?
.is_some()
{
Expand Down Expand Up @@ -491,14 +493,12 @@ where
encryption_jid.agent = 0;
}

let signal_address = encryption_jid.to_protocol_address();
// Fix: Use the normalized device_jid to lookup the bundle
// Use centralized normalization logic to avoid mismatches
encryption_jid.reset_protocol_address(&mut reusable_addr);
let lookup_jid = device_jid.normalize_for_prekey_bundle();
match prekey_bundles.get(&lookup_jid) {
Some(bundle) => {
match process_prekey_bundle(
&signal_address,
&reusable_addr,
stores.session_store,
stores.identity_store,
bundle,
Expand Down Expand Up @@ -536,7 +536,7 @@ where
// Save the new identity (this replaces the old one)
if let Err(e) = stores
.identity_store
.save_identity(&signal_address, new_identity)
.save_identity(&reusable_addr, new_identity)
.await
{
log::warn!(
Expand All @@ -554,7 +554,7 @@ where

// Retry processing the prekey bundle with the updated identity
match process_prekey_bundle(
&signal_address,
&reusable_addr,
stores.session_store,
stores.identity_store,
bundle,
Expand Down Expand Up @@ -583,7 +583,7 @@ where
// Propagate other unexpected errors
return Err(anyhow::anyhow!(
"Failed to process pre-key bundle for {}: {:?}",
signal_address,
reusable_addr,
e
));
}
Expand All @@ -592,7 +592,7 @@ where
None => {
log::warn!(
"No pre-key bundle returned for device {}. This device will be skipped for encryption.",
&signal_address
&reusable_addr
);
}
}
Expand All @@ -605,11 +605,11 @@ where

for device_jid in devices {
let encryption_jid = jid_to_encryption_jid.get(device_jid).unwrap_or(device_jid);
let signal_address = encryption_jid.to_protocol_address();
encryption_jid.reset_protocol_address(&mut reusable_addr);

match message_encrypt(
plaintext_to_encrypt,
&signal_address,
&reusable_addr,
stores.session_store,
stores.identity_store,
)
Expand Down Expand Up @@ -645,7 +645,7 @@ where
Err(e) => {
log::warn!(
"Failed to encrypt for device {}: {}. Skipping.",
&signal_address,
&reusable_addr,
e
);
}
Expand Down
Loading
Loading