Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 3 additions & 2 deletions src/client/context_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ impl SendContextResolver for Client {
&self,
jids: &[Jid],
) -> Result<HashMap<Jid, PreKeyBundle>, anyhow::Error> {
self.fetch_pre_keys(jids, None).await
// The fan-out has its own batch-level handling; it wants the bundles only.
self.fetch_pre_keys(jids, None).await.map(|o| o.bundles)
}

async fn fetch_prekeys_for_identity_check(
&self,
jids: &[Jid],
) -> Result<HashMap<Jid, PreKeyBundle>, anyhow::Error> {
) -> Result<wacore::prekeys::PreKeyFetchOutcome, anyhow::Error> {
self.fetch_pre_keys(jids, Some(PreKeyFetchReason::Identity))
.await
.map_err(|e| {
Expand Down
35 changes: 33 additions & 2 deletions src/client/sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,13 @@ impl Client {
/// `wacore::request::ServerErrorCode` to cross the crate boundary. A downcast
/// to either one alone silently answers `false` for the other, and the failure
/// mode of that is the send failing exactly as it did before.
/// The `<error code>` the server attaches to a device it no longer knows.
const UNREGISTERED_DEVICE_CODE: u16 = 406;

fn is_device_unregistered(err: &anyhow::Error) -> bool {
use crate::error::ErrorChainExt;
err.server_rejection().is_some_and(|r| r.code == 406)
err.server_rejection()
.is_some_and(|r| r.code == UNREGISTERED_DEVICE_CODE)
}

/// The distinct users named by `jids`, in first-seen order.
Expand Down Expand Up @@ -457,6 +461,30 @@ impl Client {
Err(e) => return Err(e),
};

// The server named these individually, which is the per-device signal a
// batch-wide failure cannot give: refresh exactly their device lists and
// leave the rest of the batch alone. The send continues, because the
// devices that did come back with a bundle are unaffected and skipping
// them would deliver to fewer devices for a reason that only concerns
// the named ones.
if !prekey_bundles.rejected.is_empty() {
let rejected: Vec<Jid> = prekey_bundles
.rejected
.iter()
.filter(|device| device.code == UNREGISTERED_DEVICE_CODE)
.map(|device| device.jid.clone())
.collect();
if !rejected.is_empty() {
log::debug!(
"prekey fetch rejected {} of {} device(s) as unregistered; \
refreshing their device lists",
rejected.len(),
jids.len()
);
self.invalidate_device_caches_for(&rejected).await;
}
}

let mut adapter = self.signal_adapter().await;
let mut rng = rand::make_rng::<StdRng>();

Expand All @@ -465,7 +493,10 @@ impl Client {
let mut failed_count = 0;

for jid in jids {
if let Some(bundle) = prekey_bundles.get(&jid.normalize_for_prekey_bundle()) {
if let Some(bundle) = prekey_bundles
.bundles
.get(&jid.normalize_for_prekey_bundle())
{
match self
.install_prekey_bundle_cached(jid, bundle, &mut adapter, &mut rng)
.await
Expand Down
10 changes: 5 additions & 5 deletions src/prekeys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::sync::atomic::Ordering;
use wacore::iq::prekeys::{
DigestKeyBundleSpec, PreKeyCountSpec, PreKeyFetchReason, PreKeyFetchSpec, PreKeyUploadSpec,
};
use wacore::libsignal::protocol::{KeyPair, PreKeyBundle, PublicKey};
use wacore::libsignal::protocol::{KeyPair, PublicKey};
use wacore::libsignal::store::record_helpers::encode_pre_key_record_to;
use wacore::store::commands::DeviceCommand;
use wacore_binary::Jid;
Expand Down Expand Up @@ -147,7 +147,7 @@ impl Client {
&self,
jids: &[Jid],
reason: Option<PreKeyFetchReason>,
) -> Result<std::collections::HashMap<Jid, PreKeyBundle>, anyhow::Error> {
) -> Result<wacore::prekeys::PreKeyFetchOutcome, anyhow::Error> {
let spec = match reason {
Some(r) => PreKeyFetchSpec::with_reason(jids.to_vec(), r),
None => PreKeyFetchSpec::new(jids.to_vec()),
Expand All @@ -161,13 +161,13 @@ impl Client {
// identity as the fallback in validateADVwithIdentityKey).
let spec = spec.with_account_identities(self.collect_account_identities(jids).await);

let bundles = self.execute(spec).await?;
let outcome = self.execute(spec).await?;

for jid in bundles.keys() {
for jid in outcome.bundles.keys() {
log::debug!("Successfully parsed pre-key bundle for {}", jid.observe());
}

Ok(bundles)
Ok(outcome)
}

/// Load, for each companion JID, its account (device 0) identity key from the
Expand Down
4 changes: 2 additions & 2 deletions wacore/benches/send_receive_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,8 @@ impl SendContextResolver for MockResolver {
async fn fetch_prekeys_for_identity_check(
&self,
_: &[Jid],
) -> Result<HashMap<Jid, PreKeyBundle>, anyhow::Error> {
Ok(HashMap::new())
) -> Result<wacore::prekeys::PreKeyFetchOutcome, anyhow::Error> {
Ok(wacore::prekeys::PreKeyFetchOutcome::default())
}
async fn resolve_group_info(
&self,
Expand Down
5 changes: 4 additions & 1 deletion wacore/src/client/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,13 @@ pub trait SendContextResolver: crate::sync_marker::MaybeSendSync {
jids: &[Jid],
) -> Result<HashMap<Jid, PreKeyBundle>, anyhow::Error>;

/// Returns the bundles alongside the devices the server rejected by name,
/// so a per-device rejection is not flattened into "no bundle" before the
/// fan-out can tell the two apart.
async fn fetch_prekeys_for_identity_check(
&self,
jids: &[Jid],
) -> Result<HashMap<Jid, PreKeyBundle>, anyhow::Error>;
) -> Result<crate::prekeys::PreKeyFetchOutcome, anyhow::Error>;

async fn resolve_group_info(&self, jid: &Jid) -> Result<Arc<GroupInfo>, anyhow::Error>;

Expand Down
2 changes: 1 addition & 1 deletion wacore/src/iq/prekeys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl PreKeyFetchSpec {
}

impl IqSpec for PreKeyFetchSpec {
type Response = std::collections::HashMap<Jid, PreKeyBundle>;
type Response = crate::prekeys::PreKeyFetchOutcome;

fn build_iq(&self) -> InfoQuery<'static> {
let content = PreKeyUtils::build_fetch_prekeys_request(
Expand Down
Loading
Loading