Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
92 changes: 60 additions & 32 deletions src/client/lid_pn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,39 +295,25 @@ impl Client {
resolved
}

/// Resolve the encryption JID for a given target JID.
/// This uses the same logic as the receiving path to ensure consistent
/// lock keys between sending and receiving.
///
/// For PN JIDs, this checks if a LID mapping exists and returns the LID.
/// This ensures that sending and receiving use the same session lock.
/// Mirrors WA Web `SignalAddress.toString()` (`WAWeb/Signal/Address.js`):
/// upgrade Pn → Lid and Hosted → HostedLid when a mapping is known, else
/// preserve the input.
pub(crate) async fn resolve_encryption_jid(&self, target: &Jid) -> Jid {
if target.is_lid() {
// Already a LID - use it directly
target.clone()
} else if target.is_pn() {
// PN JID - check if we have a LID mapping
if let Some(lid_user) = self.lid_pn_cache.get_current_lid(&target.user).await {
let lid_jid = Jid {
user: lid_user.into(),
server: wacore_binary::Server::Lid,
device: target.device,
agent: target.agent,
integrator: target.integrator,
};
debug!(
"[SEND-LOCK] Resolved {} to LID {} for session lock",
target, lid_jid
);
lid_jid
} else {
// No LID mapping - use PN as-is
debug!("[SEND-LOCK] No LID mapping for {}, using PN", target);
target.clone()
}
} else {
// Other server type - use as-is
target.clone()
use wacore_binary::Server;
let lid_server = match target.server {
Server::Pn => Server::Lid,
Server::Hosted => Server::HostedLid,
_ => return target.clone(),
};
match self.lid_pn_cache.get_current_lid(&target.user).await {
Some(lid_user) => Jid {
user: lid_user.into(),
server: lid_server,
device: target.device,
agent: target.agent,
integrator: target.integrator,
},
None => target.clone(),
}
}

Expand Down Expand Up @@ -549,6 +535,48 @@ mod tests {
assert_eq!(resolved, pn_jid);
}

#[tokio::test]
async fn test_resolve_encryption_jid_hosted_with_lid_upgrades_to_hosted_lid() {
let client: Arc<Client> = create_test_client().await;
let user = "55999999999";
let lid = "100000012345678";

client
.add_lid_pn_mapping(lid, user, LearningSource::PeerPnMessage)
.await
.unwrap();

let mut hosted = Jid::new(user, Server::Hosted);
hosted.device = 99;
let resolved = client.resolve_encryption_jid(&hosted).await;

assert_eq!(resolved.user, lid);
assert_eq!(resolved.server, Server::HostedLid);
assert_eq!(resolved.device, 99);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#[tokio::test]
async fn test_resolve_encryption_jid_hosted_no_mapping_keeps_hosted() {
let client: Arc<Client> = create_test_client().await;
let mut hosted = Jid::new("55999999999", Server::Hosted);
hosted.device = 99;

let resolved = client.resolve_encryption_jid(&hosted).await;

assert_eq!(resolved, hosted);
}

#[tokio::test]
async fn test_resolve_encryption_jid_preserves_hosted_lid() {
let client: Arc<Client> = create_test_client().await;
let mut hosted_lid = Jid::new("100000012345678", Server::HostedLid);
hosted_lid.device = 99;

let resolved = client.resolve_encryption_jid(&hosted_lid).await;

assert_eq!(resolved, hosted_lid);
}

#[tokio::test]
async fn test_get_lid_pn_entry_from_pn() {
let client: Arc<Client> = create_test_client().await;
Expand Down
34 changes: 34 additions & 0 deletions wacore/binary/src/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,40 @@ mod tests {
Ok(())
}

/// Pin domain_type for direct-constructed Hosted/HostedLid JIDs (default
/// `agent=0`); pre-#391 these encoded as `0` instead of `128`/`129`.
#[test]
fn test_direct_constructed_hosted_encodes_correct_domain_type() -> TestResult {
let mut hosted = Jid::new("100000000000001", jid::Server::Hosted);
hosted.device = 99;
assert_eq!(
hosted.agent, 0,
"default agent for direct construction is 0"
);

let mut hosted_lid = Jid::new("100000000000002", jid::Server::HostedLid);
hosted_lid.device = 99;
assert_eq!(hosted_lid.agent, 0);

for (jid, expected) in [(&hosted, 128u8), (&hosted_lid, 129u8)] {
let node = NodeBuilder::new("to").attr("jid", jid.clone()).build();
let mut buf = Vec::new();
Encoder::new(Cursor::new(&mut buf))?.write_node(&node)?;

let pos = buf
.iter()
.position(|&b| b == token::AD_JID)
.expect("AD_JID marker present");
assert_eq!(
buf[pos + 1],
expected,
"direct-constructed {jid} must emit domain_type {expected} \
(pre-#391 would have emitted agent=0)"
);
}
Ok(())
}

/// Regression test: strings at the PACKED_MAX boundary must be classified
/// normally, while strings above it must be emitted as raw bytes (skipping
/// SipHash/PHF classification entirely).
Expand Down
Loading