use crate::messages::MessageUtils;
use std::sync::OnceLock;
use wacore_binary::CompactString;
use wacore_binary::jid::Jid;
pub struct ResolvedGroupDevices {
devices: Vec<Jid>,
phash: OnceLock<(Jid, CompactString)>,
}
impl crate::stats::HeapSize for ResolvedGroupDevices {
fn heap_bytes(&self) -> usize {
self.devices.capacity() * size_of::<Jid>()
+ self.devices.iter().map(|j| j.heap_bytes()).sum::<usize>()
+ self
.phash
.get()
.map_or(0, |(jid, p)| jid.heap_bytes() + p.heap_bytes())
}
}
impl ResolvedGroupDevices {
pub fn new(devices: Vec<Jid>) -> Self {
Self {
devices,
phash: OnceLock::new(),
}
}
pub fn devices(&self) -> &[Jid] {
&self.devices
}
pub fn phash(&self, own_sending_jid: &Jid) -> Option<CompactString> {
if let Some((jid, hash)) = self.phash.get() {
if jid == own_sending_jid {
return Some(hash.clone());
}
return Self::compute(&self.devices, own_sending_jid);
}
let hash = Self::compute(&self.devices, own_sending_jid)?;
let _ = self.phash.set((own_sending_jid.clone(), hash.clone()));
Some(hash)
}
fn compute(devices: &[Jid], own_sending_jid: &Jid) -> Option<CompactString> {
let set = super::group::build_group_phash_set(devices, own_sending_jid);
match MessageUtils::participant_list_hash(&set) {
Ok(phash) => Some(CompactString::from(phash)),
Err(e) => {
log::warn!("Failed to compute group phash: {e:?}");
None
}
}
}
}
pub struct ResolvedDmDevices {
partitioned: super::dm::PartitionedDmDevices,
phash: OnceLock<CompactString>,
}
impl crate::stats::HeapSize for ResolvedDmDevices {
fn heap_bytes(&self) -> usize {
self.partitioned.heap_bytes() + self.phash.get().map_or(0, |p| p.heap_bytes())
}
}
impl ResolvedDmDevices {
pub fn new(all_devices: Vec<Jid>, own_jid: &Jid, own_lid: Option<&Jid>) -> Self {
Self {
partitioned: super::dm::partition_dm_devices(all_devices, own_jid, own_lid),
phash: OnceLock::new(),
}
}
pub fn devices(&self) -> &[Jid] {
self.partitioned.valid_devices()
}
pub(crate) fn recipient_devices(&self) -> &[Jid] {
self.partitioned.recipient_devices()
}
pub(crate) fn own_other_devices(&self) -> &[Jid] {
self.partitioned.own_other_devices()
}
pub fn phash(&self) -> Option<CompactString> {
if let Some(hash) = self.phash.get() {
return Some(hash.clone());
}
let hash = match MessageUtils::participant_list_hash(self.devices()) {
Ok(phash) => CompactString::from(phash),
Err(e) => {
log::warn!("Failed to compute DM phash: {e:?}");
return None;
}
};
let _ = self.phash.set(hash.clone());
Some(hash)
}
}
impl std::fmt::Debug for ResolvedDmDevices {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ResolvedDmDevices")
.field("devices", &self.devices().len())
.field("recipients", &self.recipient_devices().len())
.field("phash_warm", &self.phash.get().is_some())
.finish()
}
}
impl std::fmt::Debug for ResolvedGroupDevices {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ResolvedGroupDevices")
.field("devices", &self.devices.len())
.field("phash_warm", &self.phash.get().is_some())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn jid(user: &str, device: u16) -> Jid {
let mut j = Jid::lid(user);
j.device = device;
j
}
#[test]
fn phash_memo_matches_direct_compute_and_pins_sender() {
let devices = vec![jid("100000000000001", 0), jid("100000000000002", 3)];
let own = jid("100000000000009", 0);
let other = jid("100000000000008", 0);
let resolved = ResolvedGroupDevices::new(devices.clone());
let direct = {
let set = crate::send::group::build_group_phash_set(&devices, &own);
MessageUtils::participant_list_hash(&set).unwrap()
};
let first = resolved.phash(&own).expect("phash");
assert_eq!(first.as_str(), direct);
assert!(resolved.phash.get().is_some(), "memo warmed on first use");
assert_eq!(resolved.phash(&own).expect("hit"), first);
let other_direct = {
let set = crate::send::group::build_group_phash_set(&devices, &other);
MessageUtils::participant_list_hash(&set).unwrap()
};
assert_eq!(
resolved.phash(&other).expect("bypass").as_str(),
other_direct
);
assert_eq!(
resolved.phash.get().expect("still pinned").0,
own,
"memo stays pinned to the first sender"
);
assert_ne!(
first.as_str(),
other_direct,
"senders must differ for this test"
);
}
}