deltachat/
contact.rs

1//! Contacts module
2
3use std::cmp::Reverse;
4use std::collections::{BinaryHeap, HashSet};
5use std::fmt;
6use std::path::{Path, PathBuf};
7use std::time::UNIX_EPOCH;
8
9use anyhow::{Context as _, Result, bail, ensure};
10use async_channel::{self as channel, Receiver, Sender};
11use base64::Engine as _;
12pub use deltachat_contact_tools::may_be_valid_addr;
13use deltachat_contact_tools::{
14    self as contact_tools, ContactAddress, VcardContact, addr_normalize, sanitize_name,
15    sanitize_name_and_addr,
16};
17use deltachat_derive::{FromSql, ToSql};
18use rusqlite::OptionalExtension;
19use serde::{Deserialize, Serialize};
20use tokio::task;
21use tokio::time::{Duration, timeout};
22
23use crate::blob::BlobObject;
24use crate::chat::ChatId;
25use crate::color::str_to_color;
26use crate::config::Config;
27use crate::constants::{self, Blocked, Chattype};
28use crate::context::Context;
29use crate::events::EventType;
30use crate::key::{
31    DcKey, Fingerprint, SignedPublicKey, load_self_public_key, self_fingerprint,
32    self_fingerprint_opt,
33};
34use crate::log::{LogExt, warn};
35use crate::message::MessageState;
36use crate::mimeparser::AvatarAction;
37use crate::param::{Param, Params};
38use crate::sync::{self, Sync::*};
39use crate::tools::{SystemTime, duration_to_str, get_abs_path, time, to_lowercase};
40use crate::{chat, chatlist_events, ensure_and_debug_assert_ne, stock_str};
41
42/// Time during which a contact is considered as seen recently.
43const SEEN_RECENTLY_SECONDS: i64 = 600;
44
45/// Contact ID, including reserved IDs.
46///
47/// Some contact IDs are reserved to identify special contacts.  This
48/// type can represent both the special as well as normal contacts.
49#[derive(
50    Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
51)]
52pub struct ContactId(u32);
53
54impl ContactId {
55    /// Undefined contact. Used as a placeholder for trashed messages.
56    pub const UNDEFINED: ContactId = ContactId::new(0);
57
58    /// The owner of the account.
59    ///
60    /// The email-address is set by `set_config` using "addr".
61    pub const SELF: ContactId = ContactId::new(1);
62
63    /// ID of the contact for info messages.
64    pub const INFO: ContactId = ContactId::new(2);
65
66    /// ID of the contact for device messages.
67    pub const DEVICE: ContactId = ContactId::new(5);
68    pub(crate) const LAST_SPECIAL: ContactId = ContactId::new(9);
69
70    /// Address to go with [`ContactId::DEVICE`].
71    ///
72    /// This is used by APIs which need to return an email address for this contact.
73    pub const DEVICE_ADDR: &'static str = "device@localhost";
74
75    /// Creates a new [`ContactId`].
76    pub const fn new(id: u32) -> ContactId {
77        ContactId(id)
78    }
79
80    /// Whether this is a special [`ContactId`].
81    ///
82    /// Some [`ContactId`]s are reserved for special contacts like [`ContactId::SELF`],
83    /// [`ContactId::INFO`] and [`ContactId::DEVICE`].  This function indicates whether this
84    /// [`ContactId`] is any of the reserved special [`ContactId`]s (`true`) or whether it
85    /// is the [`ContactId`] of a real contact (`false`).
86    pub fn is_special(&self) -> bool {
87        self.0 <= Self::LAST_SPECIAL.0
88    }
89
90    /// Numerical representation of the [`ContactId`].
91    ///
92    /// Each contact ID has a unique numerical representation which is used in the database
93    /// (via [`rusqlite::ToSql`]) and also for FFI purposes.  In Rust code you should never
94    /// need to use this directly.
95    pub const fn to_u32(&self) -> u32 {
96        self.0
97    }
98
99    /// Sets display name for existing contact.
100    ///
101    /// Display name may be an empty string,
102    /// in which case the name displayed in the UI
103    /// for this contact will switch to the
104    /// contact's authorized name.
105    pub async fn set_name(self, context: &Context, name: &str) -> Result<()> {
106        self.set_name_ex(context, Sync, name).await
107    }
108
109    pub(crate) async fn set_name_ex(
110        self,
111        context: &Context,
112        sync: sync::Sync,
113        name: &str,
114    ) -> Result<()> {
115        let row = context
116            .sql
117            .transaction(|transaction| {
118                let is_changed = transaction.execute(
119                    "UPDATE contacts SET name=?1 WHERE id=?2 AND name!=?1",
120                    (name, self),
121                )? > 0;
122                if is_changed {
123                    update_chat_names(context, transaction, self)?;
124                    let (addr, fingerprint) = transaction.query_row(
125                        "SELECT addr, fingerprint FROM contacts WHERE id=?",
126                        (self,),
127                        |row| {
128                            let addr: String = row.get(0)?;
129                            let fingerprint: String = row.get(1)?;
130                            Ok((addr, fingerprint))
131                        },
132                    )?;
133                    Ok(Some((addr, fingerprint)))
134                } else {
135                    Ok(None)
136                }
137            })
138            .await?;
139        if row.is_some() {
140            context.emit_event(EventType::ContactsChanged(Some(self)));
141        }
142
143        if sync.into()
144            && let Some((addr, fingerprint)) = row
145        {
146            if fingerprint.is_empty() {
147                chat::sync(
148                    context,
149                    chat::SyncId::ContactAddr(addr),
150                    chat::SyncAction::Rename(name.to_string()),
151                )
152                .await
153                .log_err(context)
154                .ok();
155            } else {
156                chat::sync(
157                    context,
158                    chat::SyncId::ContactFingerprint(fingerprint),
159                    chat::SyncAction::Rename(name.to_string()),
160                )
161                .await
162                .log_err(context)
163                .ok();
164            }
165        }
166        Ok(())
167    }
168
169    /// Mark contact as bot.
170    pub(crate) async fn mark_bot(&self, context: &Context, is_bot: bool) -> Result<()> {
171        context
172            .sql
173            .execute("UPDATE contacts SET is_bot=? WHERE id=?;", (is_bot, self.0))
174            .await?;
175        Ok(())
176    }
177
178    /// Reset gossip timestamp in all chats with this contact.
179    pub(crate) async fn regossip_keys(&self, context: &Context) -> Result<()> {
180        context
181            .sql
182            .execute(
183                "UPDATE chats
184                 SET gossiped_timestamp=0
185                 WHERE EXISTS (SELECT 1 FROM chats_contacts
186                               WHERE chats_contacts.chat_id=chats.id
187                               AND chats_contacts.contact_id=?
188                               AND chats_contacts.add_timestamp >= chats_contacts.remove_timestamp)",
189                (self,),
190            )
191            .await?;
192        Ok(())
193    }
194
195    /// Updates the origin of the contacts, but only if `origin` is higher than the current one.
196    pub(crate) async fn scaleup_origin(
197        context: &Context,
198        ids: &[Self],
199        origin: Origin,
200    ) -> Result<()> {
201        context
202            .sql
203            .transaction(|transaction| {
204                let mut stmt = transaction
205                    .prepare("UPDATE contacts SET origin=?1 WHERE id = ?2 AND origin < ?1")?;
206                for id in ids {
207                    stmt.execute((origin, id))?;
208                }
209                Ok(())
210            })
211            .await?;
212        Ok(())
213    }
214
215    /// Returns contact address.
216    pub async fn addr(&self, context: &Context) -> Result<String> {
217        let addr = context
218            .sql
219            .query_row("SELECT addr FROM contacts WHERE id=?", (self,), |row| {
220                let addr: String = row.get(0)?;
221                Ok(addr)
222            })
223            .await?;
224        Ok(addr)
225    }
226}
227
228impl fmt::Display for ContactId {
229    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
230        if *self == ContactId::UNDEFINED {
231            write!(f, "Contact#Undefined")
232        } else if *self == ContactId::SELF {
233            write!(f, "Contact#Self")
234        } else if *self == ContactId::INFO {
235            write!(f, "Contact#Info")
236        } else if *self == ContactId::DEVICE {
237            write!(f, "Contact#Device")
238        } else if self.is_special() {
239            write!(f, "Contact#Special{}", self.0)
240        } else {
241            write!(f, "Contact#{}", self.0)
242        }
243    }
244}
245
246/// Allow converting [`ContactId`] to an SQLite type.
247impl rusqlite::types::ToSql for ContactId {
248    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
249        let val = rusqlite::types::Value::Integer(i64::from(self.0));
250        let out = rusqlite::types::ToSqlOutput::Owned(val);
251        Ok(out)
252    }
253}
254
255/// Allow converting an SQLite integer directly into [`ContactId`].
256impl rusqlite::types::FromSql for ContactId {
257    fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
258        i64::column_result(value).and_then(|val| {
259            val.try_into()
260                .map(ContactId::new)
261                .map_err(|_| rusqlite::types::FromSqlError::OutOfRange(val))
262        })
263    }
264}
265
266/// Returns a vCard containing contacts with the given ids.
267pub async fn make_vcard(context: &Context, contacts: &[ContactId]) -> Result<String> {
268    let now = time();
269    let mut vcard_contacts = Vec::with_capacity(contacts.len());
270    for id in contacts {
271        let c = Contact::get_by_id(context, *id).await?;
272        let key = c.public_key(context).await?.map(|k| k.to_base64());
273        let profile_image = match c.get_profile_image_ex(context, false).await? {
274            None => None,
275            Some(path) => tokio::fs::read(path)
276                .await
277                .log_err(context)
278                .ok()
279                .map(|data| base64::engine::general_purpose::STANDARD.encode(data)),
280        };
281        vcard_contacts.push(VcardContact {
282            addr: c.addr,
283            authname: c.authname,
284            key,
285            profile_image,
286            biography: Some(c.status).filter(|s| !s.is_empty()),
287            // Use the current time to not reveal our or contact's online time.
288            timestamp: Ok(now),
289        });
290    }
291
292    // XXX: newline at the end of vCard is trimmed
293    // for compatibility with core <=1.155.3
294    // Newer core should be able to deal with
295    // trailing CRLF as the fix
296    // <https://github.com/deltachat/deltachat-core-rust/pull/6522>
297    // is merged.
298    Ok(contact_tools::make_vcard(&vcard_contacts)
299        .trim_end()
300        .to_string())
301}
302
303/// Imports contacts from the given vCard.
304///
305/// Returns the ids of successfully processed contacts in the order they appear in `vcard`,
306/// regardless of whether they are just created, modified or left untouched.
307pub async fn import_vcard(context: &Context, vcard: &str) -> Result<Vec<ContactId>> {
308    let contacts = contact_tools::parse_vcard(vcard);
309    let mut contact_ids = Vec::with_capacity(contacts.len());
310    for c in &contacts {
311        let Ok(id) = import_vcard_contact(context, c)
312            .await
313            .with_context(|| format!("import_vcard_contact() failed for {}", c.addr))
314            .log_err(context)
315        else {
316            continue;
317        };
318        contact_ids.push(id);
319    }
320    Ok(contact_ids)
321}
322
323async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Result<ContactId> {
324    let addr = ContactAddress::new(&contact.addr).context("Invalid address")?;
325    // Importing a vCard is also an explicit user action like creating a chat with the contact. We
326    // mustn't use `Origin::AddressBook` here because the vCard may be created not by us, also we
327    // want `contact.authname` to be saved as the authname and not a locally given name.
328    let origin = Origin::CreateChat;
329    let key = contact.key.as_ref().and_then(|k| {
330        SignedPublicKey::from_base64(k)
331            .with_context(|| {
332                format!(
333                    "import_vcard_contact: Cannot decode key for {}",
334                    contact.addr
335                )
336            })
337            .log_err(context)
338            .ok()
339    });
340
341    let fingerprint;
342    if let Some(public_key) = key {
343        fingerprint = public_key.dc_fingerprint().hex();
344
345        context
346            .sql
347            .execute(
348                "INSERT INTO public_keys (fingerprint, public_key)
349                 VALUES (?, ?)
350                 ON CONFLICT (fingerprint)
351                 DO NOTHING",
352                (&fingerprint, public_key.to_bytes()),
353            )
354            .await?;
355    } else {
356        fingerprint = String::new();
357    }
358
359    let (id, modified) =
360        match Contact::add_or_lookup_ex(context, &contact.authname, &addr, &fingerprint, origin)
361            .await
362        {
363            Err(e) => return Err(e).context("Contact::add_or_lookup() failed"),
364            Ok((ContactId::SELF, _)) => return Ok(ContactId::SELF),
365            Ok(val) => val,
366        };
367    if modified != Modifier::None {
368        context.emit_event(EventType::ContactsChanged(Some(id)));
369    }
370    if modified != Modifier::Created {
371        return Ok(id);
372    }
373    let path = match &contact.profile_image {
374        Some(image) => match BlobObject::store_from_base64(context, image)? {
375            None => {
376                warn!(
377                    context,
378                    "import_vcard_contact: Could not decode avatar for {}.", contact.addr
379                );
380                None
381            }
382            Some(path) => Some(path),
383        },
384        None => None,
385    };
386    if let Some(path) = path
387        && let Err(e) = set_profile_image(context, id, &AvatarAction::Change(path)).await
388    {
389        warn!(
390            context,
391            "import_vcard_contact: Could not set avatar for {}: {e:#}.", contact.addr
392        );
393    }
394    if let Some(biography) = &contact.biography
395        && let Err(e) = set_status(context, id, biography.to_owned()).await
396    {
397        warn!(
398            context,
399            "import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr
400        );
401    }
402    Ok(id)
403}
404
405/// An object representing a single contact in memory.
406///
407/// The contact object is not updated.
408/// If you want an update, you have to recreate the object.
409///
410/// The library makes sure
411/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
412/// *Given-names* as "Daddy" or "Honey" are not used there.
413/// For this purpose, internally, two names are tracked -
414/// authorized name and given name.
415/// By default, these names are equal, but functions working with contact names
416/// only affect the given name.
417#[derive(Debug)]
418pub struct Contact {
419    /// The contact ID.
420    pub id: ContactId,
421
422    /// Contact name. It is recommended to use `Contact::get_name`,
423    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
424    /// May be empty, initially set to `authname`.
425    name: String,
426
427    /// Name authorized by the contact himself. Only this name may be spread to others,
428    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
429    /// to access this field.
430    authname: String,
431
432    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
433    addr: String,
434
435    /// OpenPGP key fingerprint.
436    /// Non-empty iff the contact is a key-contact,
437    /// identified by this fingerprint.
438    fingerprint: Option<String>,
439
440    /// Blocked state. Use contact_is_blocked to access this field.
441    pub blocked: bool,
442
443    /// Time when the contact was seen last time, Unix time in seconds.
444    last_seen: i64,
445
446    /// The origin/source of the contact.
447    pub origin: Origin,
448
449    /// Parameters as Param::ProfileImage
450    pub param: Params,
451
452    /// Last seen message signature for this contact, to be displayed in the profile.
453    status: String,
454
455    /// If the contact is a bot.
456    is_bot: bool,
457}
458
459/// Possible origins of a contact.
460#[derive(
461    Debug,
462    Default,
463    Clone,
464    Copy,
465    PartialEq,
466    Eq,
467    PartialOrd,
468    Ord,
469    FromPrimitive,
470    ToPrimitive,
471    FromSql,
472    ToSql,
473)]
474#[repr(u32)]
475pub enum Origin {
476    /// Unknown origin. Can be used as a minimum origin to specify that the caller does not care
477    /// about origin of the contact.
478    #[default]
479    Unknown = 0,
480
481    /// The contact is a mailing list address, needed to unblock mailing lists
482    MailinglistAddress = 0x2,
483
484    /// Hidden on purpose, e.g. addresses with the word "noreply" in it
485    Hidden = 0x8,
486
487    /// From: of incoming messages of unknown sender
488    IncomingUnknownFrom = 0x10,
489
490    /// Cc: of incoming messages of unknown sender
491    IncomingUnknownCc = 0x20,
492
493    /// To: of incoming messages of unknown sender
494    IncomingUnknownTo = 0x40,
495
496    /// Address scanned but not verified.
497    UnhandledQrScan = 0x80,
498
499    /// Address scanned from a SecureJoin QR code, but not verified yet.
500    UnhandledSecurejoinQrScan = 0x81,
501
502    /// Reply-To: of incoming message of known sender
503    /// Contacts with at least this origin value are shown in the contact list.
504    IncomingReplyTo = 0x100,
505
506    /// Cc: of incoming message of known sender
507    IncomingCc = 0x200,
508
509    /// additional To:'s of incoming message of known sender
510    IncomingTo = 0x400,
511
512    /// a chat was manually created for this user, but no message yet sent
513    CreateChat = 0x800,
514
515    /// message sent by us
516    OutgoingBcc = 0x1000,
517
518    /// message sent by us
519    OutgoingCc = 0x2000,
520
521    /// message sent by us
522    OutgoingTo = 0x4000,
523
524    /// internal use
525    Internal = 0x40000,
526
527    /// address is in our address book
528    AddressBook = 0x80000,
529
530    /// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling contact_is_verified() !
531    SecurejoinInvited = 0x0100_0000,
532
533    /// Set on Bob's side for contacts scanned from a QR code.
534    /// Only means the contact has been scanned from the QR code,
535    /// but does not mean that securejoin succeeded
536    /// or the key has not changed since the last scan.
537    /// Getting the current key verification status requires calling contact_is_verified() !
538    SecurejoinJoined = 0x0200_0000,
539
540    /// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
541    ManuallyCreated = 0x0400_0000,
542}
543
544impl Origin {
545    /// Contacts that are known, i. e. they came in via accepted contacts or
546    /// themselves an accepted contact. Known contacts are shown in the
547    /// contact list when one creates a chat and wants to add members etc.
548    pub fn is_known(self) -> bool {
549        self >= Origin::IncomingReplyTo
550    }
551}
552
553#[derive(Debug, PartialEq, Eq, Clone, Copy)]
554pub(crate) enum Modifier {
555    None,
556    Modified,
557    Created,
558}
559
560impl Contact {
561    /// Loads a single contact object from the database.
562    ///
563    /// Returns an error if the contact does not exist.
564    ///
565    /// For contact ContactId::SELF (1), the function returns sth.
566    /// like "Me" in the selected language and the email address
567    /// defined by set_config().
568    ///
569    /// For contact ContactId::DEVICE, the function overrides
570    /// the contact name and status with localized address.
571    pub async fn get_by_id(context: &Context, contact_id: ContactId) -> Result<Self> {
572        let contact = Self::get_by_id_optional(context, contact_id)
573            .await?
574            .with_context(|| format!("contact {contact_id} not found"))?;
575        Ok(contact)
576    }
577
578    /// Loads a single contact object from the database.
579    ///
580    /// Similar to [`Contact::get_by_id()`] but returns `None` if the contact does not exist.
581    pub async fn get_by_id_optional(
582        context: &Context,
583        contact_id: ContactId,
584    ) -> Result<Option<Self>> {
585        if let Some(mut contact) = context
586            .sql
587            .query_row_optional(
588                "SELECT c.name, c.addr, c.origin, c.blocked, c.last_seen,
589                c.authname, c.param, c.status, c.is_bot, c.fingerprint
590               FROM contacts c
591              WHERE c.id=?;",
592                (contact_id,),
593                |row| {
594                    let name: String = row.get(0)?;
595                    let addr: String = row.get(1)?;
596                    let origin: Origin = row.get(2)?;
597                    let blocked: Option<bool> = row.get(3)?;
598                    let last_seen: i64 = row.get(4)?;
599                    let authname: String = row.get(5)?;
600                    let param: String = row.get(6)?;
601                    let status: Option<String> = row.get(7)?;
602                    let is_bot: bool = row.get(8)?;
603                    let fingerprint: Option<String> =
604                        Some(row.get(9)?).filter(|s: &String| !s.is_empty());
605                    let contact = Self {
606                        id: contact_id,
607                        name,
608                        authname,
609                        addr,
610                        fingerprint,
611                        blocked: blocked.unwrap_or_default(),
612                        last_seen,
613                        origin,
614                        param: param.parse().unwrap_or_default(),
615                        status: status.unwrap_or_default(),
616                        is_bot,
617                    };
618                    Ok(contact)
619                },
620            )
621            .await?
622        {
623            if contact_id == ContactId::SELF {
624                contact.name = stock_str::self_msg(context).await;
625                contact.authname = context
626                    .get_config(Config::Displayname)
627                    .await?
628                    .unwrap_or_default();
629                contact.addr = context
630                    .get_config(Config::ConfiguredAddr)
631                    .await?
632                    .unwrap_or_default();
633                if let Some(self_fp) = self_fingerprint_opt(context).await? {
634                    contact.fingerprint = Some(self_fp.to_string());
635                }
636                contact.status = context
637                    .get_config(Config::Selfstatus)
638                    .await?
639                    .unwrap_or_default();
640            } else if contact_id == ContactId::DEVICE {
641                contact.name = stock_str::device_messages(context).await;
642                contact.addr = ContactId::DEVICE_ADDR.to_string();
643                contact.status = stock_str::device_messages_hint(context).await;
644            }
645            Ok(Some(contact))
646        } else {
647            Ok(None)
648        }
649    }
650
651    /// Returns `true` if this contact is blocked.
652    pub fn is_blocked(&self) -> bool {
653        self.blocked
654    }
655
656    /// Returns last seen timestamp.
657    pub fn last_seen(&self) -> i64 {
658        self.last_seen
659    }
660
661    /// Returns `true` if this contact was seen recently.
662    pub fn was_seen_recently(&self) -> bool {
663        time() - self.last_seen <= SEEN_RECENTLY_SECONDS
664    }
665
666    /// Check if a contact is blocked.
667    pub async fn is_blocked_load(context: &Context, id: ContactId) -> Result<bool> {
668        let blocked = context
669            .sql
670            .query_row("SELECT blocked FROM contacts WHERE id=?", (id,), |row| {
671                let blocked: bool = row.get(0)?;
672                Ok(blocked)
673            })
674            .await?;
675        Ok(blocked)
676    }
677
678    /// Block the given contact.
679    pub async fn block(context: &Context, id: ContactId) -> Result<()> {
680        set_blocked(context, Sync, id, true).await
681    }
682
683    /// Unblock the given contact.
684    pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
685        set_blocked(context, Sync, id, false).await
686    }
687
688    /// Add a single contact as a result of an _explicit_ user action.
689    ///
690    /// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
691    /// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
692    ///
693    /// To add a number of contacts, see `add_address_book()` which is much faster for adding
694    /// a bunch of addresses.
695    ///
696    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
697    pub async fn create(context: &Context, name: &str, addr: &str) -> Result<ContactId> {
698        Self::create_ex(context, Sync, name, addr).await
699    }
700
701    pub(crate) async fn create_ex(
702        context: &Context,
703        sync: sync::Sync,
704        name: &str,
705        addr: &str,
706    ) -> Result<ContactId> {
707        let (name, addr) = sanitize_name_and_addr(name, addr);
708        let addr = ContactAddress::new(&addr)?;
709
710        let (contact_id, sth_modified) =
711            Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
712                .await
713                .context("add_or_lookup")?;
714        let blocked = Contact::is_blocked_load(context, contact_id).await?;
715        match sth_modified {
716            Modifier::None => {}
717            Modifier::Modified | Modifier::Created => {
718                context.emit_event(EventType::ContactsChanged(Some(contact_id)))
719            }
720        }
721        if blocked {
722            set_blocked(context, Nosync, contact_id, false).await?;
723        }
724
725        if sync.into() && sth_modified != Modifier::None {
726            chat::sync(
727                context,
728                chat::SyncId::ContactAddr(addr.to_string()),
729                chat::SyncAction::Rename(name.to_string()),
730            )
731            .await
732            .log_err(context)
733            .ok();
734        }
735        Ok(contact_id)
736    }
737
738    /// Mark messages from a contact as noticed.
739    pub async fn mark_noticed(context: &Context, id: ContactId) -> Result<()> {
740        context
741            .sql
742            .execute(
743                "UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
744                (MessageState::InNoticed, id, MessageState::InFresh),
745            )
746            .await?;
747        Ok(())
748    }
749
750    /// Returns whether contact is a bot.
751    pub fn is_bot(&self) -> bool {
752        self.is_bot
753    }
754
755    /// Looks up a known and unblocked contact with a given e-mail address.
756    /// To get a list of all known and unblocked contacts, use contacts_get_contacts().
757    ///
758    ///
759    /// **POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
760    /// (e.g. an address-contact and a key-contact),
761    /// this looks up the most recently seen contact,
762    /// i.e. which contact is returned depends on which contact last sent a message.
763    /// If the user just clicked on a mailto: link, then this is the best thing you can do.
764    /// But **DO NOT** internally represent contacts by their email address
765    /// and do not use this function to look them up;
766    /// otherwise this function will sometimes look up the wrong contact.
767    /// Instead, you should internally represent contacts by their ids.
768    ///
769    /// Known and unblocked contacts will be returned by `get_contacts()`.
770    ///
771    /// To validate an e-mail address independently of the contact database
772    /// use `may_be_valid_addr()`.
773    ///
774    /// Returns the contact ID of the contact belonging to the e-mail address or 0 if there is no
775    /// contact that is or was introduced by an accepted contact.
776    pub async fn lookup_id_by_addr(
777        context: &Context,
778        addr: &str,
779        min_origin: Origin,
780    ) -> Result<Option<ContactId>> {
781        Self::lookup_id_by_addr_ex(context, addr, min_origin, Some(Blocked::Not)).await
782    }
783
784    /// The same as `lookup_id_by_addr()`, but internal function. Currently also allows looking up
785    /// not unblocked contacts.
786    pub(crate) async fn lookup_id_by_addr_ex(
787        context: &Context,
788        addr: &str,
789        min_origin: Origin,
790        blocked: Option<Blocked>,
791    ) -> Result<Option<ContactId>> {
792        if addr.is_empty() {
793            bail!("lookup_id_by_addr: empty address");
794        }
795
796        let addr_normalized = addr_normalize(addr);
797
798        if context.is_configured().await? && context.is_self_addr(addr).await? {
799            return Ok(Some(ContactId::SELF));
800        }
801
802        let id = context
803            .sql
804            .query_get_value(
805                "SELECT id FROM contacts
806                 WHERE addr=?1 COLLATE NOCASE
807                     AND id>?2 AND origin>=?3 AND (? OR blocked=?)
808                 ORDER BY
809                     (
810                         SELECT COUNT(*) FROM chats c
811                         INNER JOIN chats_contacts cc
812                         ON c.id=cc.chat_id
813                         WHERE c.type=?
814                             AND c.id>?
815                             AND c.blocked=?
816                             AND cc.contact_id=contacts.id
817                     ) DESC,
818                     last_seen DESC, fingerprint DESC
819                 LIMIT 1",
820                (
821                    &addr_normalized,
822                    ContactId::LAST_SPECIAL,
823                    min_origin as u32,
824                    blocked.is_none(),
825                    blocked.unwrap_or(Blocked::Not),
826                    Chattype::Single,
827                    constants::DC_CHAT_ID_LAST_SPECIAL,
828                    blocked.unwrap_or(Blocked::Not),
829                ),
830            )
831            .await?;
832        Ok(id)
833    }
834
835    pub(crate) async fn add_or_lookup(
836        context: &Context,
837        name: &str,
838        addr: &ContactAddress,
839        origin: Origin,
840    ) -> Result<(ContactId, Modifier)> {
841        Self::add_or_lookup_ex(context, name, addr, "", origin).await
842    }
843
844    /// Lookup a contact and create it if it does not exist yet.
845    /// If `fingerprint` is non-empty, a key-contact with this fingerprint is added / looked up.
846    /// Otherwise, an address-contact with `addr` is added / looked up.
847    /// A name and an "origin" can be given.
848    ///
849    /// The "origin" is where the address comes from -
850    /// from-header, cc-header, addressbook, qr, manual-edit etc.
851    /// In general, "better" origins overwrite the names of "worse" origins -
852    /// Eg. if we got a name in cc-header and later in from-header, the name will change -
853    /// this does not happen the other way round.
854    ///
855    /// The "best" origin are manually created contacts -
856    /// names given manually can only be overwritten by further manual edits
857    /// (until they are set empty again or reset to the name seen in the From-header).
858    ///
859    /// These manually edited names are _never_ used for sending on the wire -
860    /// this should avoid sending sth. as "Mama" or "Daddy" to some 3rd party.
861    /// Instead, for the wire, we use so called "authnames"
862    /// that can only be set and updated by a From-header.
863    ///
864    /// The different names used in the function are:
865    /// - "name": name passed as function argument, belonging to the given origin
866    /// - "row_name": current name used in the database, typically set to "name"
867    /// - "row_authname": name as authorized from a contact, set only through a From-header
868    ///   Depending on the origin, both, "row_name" and "row_authname" are updated from "name".
869    ///
870    /// Returns the contact_id and a `Modifier` value indicating if a modification occurred.
871    pub(crate) async fn add_or_lookup_ex(
872        context: &Context,
873        name: &str,
874        addr: &str,
875        fingerprint: &str,
876        mut origin: Origin,
877    ) -> Result<(ContactId, Modifier)> {
878        let mut sth_modified = Modifier::None;
879
880        ensure!(
881            !addr.is_empty() || !fingerprint.is_empty(),
882            "Can not add_or_lookup empty address"
883        );
884        ensure!(origin != Origin::Unknown, "Missing valid origin");
885
886        if context.is_configured().await? && context.is_self_addr(addr).await? {
887            return Ok((ContactId::SELF, sth_modified));
888        }
889
890        if !fingerprint.is_empty() && context.is_configured().await? {
891            let fingerprint_self = self_fingerprint(context)
892                .await
893                .context("self_fingerprint")?;
894            if fingerprint == fingerprint_self {
895                return Ok((ContactId::SELF, sth_modified));
896            }
897        }
898
899        let mut name = sanitize_name(name);
900        if origin <= Origin::OutgoingTo {
901            // The user may accidentally have written to a "noreply" address with another MUA:
902            if addr.contains("noreply")
903                || addr.contains("no-reply")
904                || addr.starts_with("notifications@")
905                // Filter out use-once addresses (like reply+AEJDGPOECLAP...@reply.github.com):
906                || (addr.len() > 50 && addr.contains('+'))
907            {
908                info!(context, "hiding contact {}", addr);
909                origin = Origin::Hidden;
910                // For these kind of email addresses, sender and address often don't belong together
911                // (like hocuri <notifications@github.com>). In this example, hocuri shouldn't
912                // be saved as the displayname for notifications@github.com.
913                name = "".to_string();
914            }
915        }
916
917        // If the origin indicates that user entered the contact manually, from the address book or
918        // from the QR-code scan (potentially from the address book of their other phone), then name
919        // should go into the "name" column and never into "authname" column, to avoid leaking it
920        // into the network.
921        let manual = matches!(
922            origin,
923            Origin::ManuallyCreated | Origin::AddressBook | Origin::UnhandledQrScan
924        );
925
926        let mut update_addr = false;
927
928        let row_id = context
929            .sql
930            .transaction(|transaction| {
931                let row = transaction
932                    .query_row(
933                        "SELECT id, name, addr, origin, authname
934                         FROM contacts
935                         WHERE fingerprint=?1 AND
936                         (?1<>'' OR addr=?2 COLLATE NOCASE)",
937                        (fingerprint, addr),
938                        |row| {
939                            let row_id: u32 = row.get(0)?;
940                            let row_name: String = row.get(1)?;
941                            let row_addr: String = row.get(2)?;
942                            let row_origin: Origin = row.get(3)?;
943                            let row_authname: String = row.get(4)?;
944
945                            Ok((row_id, row_name, row_addr, row_origin, row_authname))
946                        },
947                    )
948                    .optional()?;
949
950                let row_id;
951                if let Some((id, row_name, row_addr, row_origin, row_authname)) = row {
952                    let update_name = manual && name != row_name;
953                    let update_authname = !manual
954                        && name != row_authname
955                        && !name.is_empty()
956                        && (origin >= row_origin
957                            || origin == Origin::IncomingUnknownFrom
958                            || row_authname.is_empty());
959
960                    row_id = id;
961                    if origin >= row_origin && addr != row_addr {
962                        update_addr = true;
963                    }
964                    if update_name || update_authname || update_addr || origin > row_origin {
965                        let new_name = if update_name {
966                            name.to_string()
967                        } else {
968                            row_name
969                        };
970
971                        transaction.execute(
972                            "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
973                            (
974                                new_name,
975                                if update_addr {
976                                    addr.to_string()
977                                } else {
978                                    row_addr
979                                },
980                                if origin > row_origin {
981                                    origin
982                                } else {
983                                    row_origin
984                                },
985                                if update_authname {
986                                    name.to_string()
987                                } else {
988                                    row_authname
989                                },
990                                row_id,
991                            ),
992                        )?;
993
994                        if update_name || update_authname {
995                            let contact_id = ContactId::new(row_id);
996                            update_chat_names(context, transaction, contact_id)?;
997                        }
998                        sth_modified = Modifier::Modified;
999                    }
1000                } else {
1001                    let update_name = manual;
1002                    let update_authname = !manual;
1003
1004                    transaction.execute(
1005                        "INSERT INTO contacts (name, addr, fingerprint, origin, authname)
1006                         VALUES (?, ?, ?, ?, ?);",
1007                        (
1008                            if update_name { &name } else { "" },
1009                            &addr,
1010                            fingerprint,
1011                            origin,
1012                            if update_authname { &name } else { "" },
1013                        ),
1014                    )?;
1015
1016                    sth_modified = Modifier::Created;
1017                    row_id = u32::try_from(transaction.last_insert_rowid())?;
1018                    if fingerprint.is_empty() {
1019                        info!(context, "Added contact id={row_id} addr={addr}.");
1020                    } else {
1021                        info!(
1022                            context,
1023                            "Added contact id={row_id} fpr={fingerprint} addr={addr}."
1024                        );
1025                    }
1026                }
1027                Ok(row_id)
1028            })
1029            .await?;
1030
1031        let contact_id = ContactId::new(row_id);
1032
1033        Ok((contact_id, sth_modified))
1034    }
1035
1036    /// Add a number of contacts.
1037    ///
1038    /// Typically used to add the whole address book from the OS. As names here are typically not
1039    /// well formatted, we call `normalize()` for each name given.
1040    ///
1041    /// No email-address is added twice.
1042    /// Trying to add email-addresses that are already in the contact list,
1043    /// results in updating the name unless the name was changed manually by the user.
1044    /// If any email-address or any name is really updated,
1045    /// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
1046    ///
1047    /// To add a single contact entered by the user, you should prefer `Contact::create`,
1048    /// however, for adding a bunch of addresses, this function is much faster.
1049    ///
1050    /// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
1051    ///
1052    /// Returns the number of modified contacts.
1053    pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
1054        let mut modify_cnt = 0;
1055
1056        for (name, addr) in split_address_book(addr_book) {
1057            let (name, addr) = sanitize_name_and_addr(name, addr);
1058            match ContactAddress::new(&addr) {
1059                Ok(addr) => {
1060                    match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
1061                        Ok((_, modified)) => {
1062                            if modified != Modifier::None {
1063                                modify_cnt += 1
1064                            }
1065                        }
1066                        Err(err) => {
1067                            warn!(
1068                                context,
1069                                "Failed to add address {} from address book: {}", addr, err
1070                            );
1071                        }
1072                    }
1073                }
1074                Err(err) => {
1075                    warn!(context, "{:#}.", err);
1076                }
1077            }
1078        }
1079        if modify_cnt > 0 {
1080            context.emit_event(EventType::ContactsChanged(None));
1081        }
1082
1083        Ok(modify_cnt)
1084    }
1085
1086    /// Returns known and unblocked contacts.
1087    ///
1088    /// To get information about a single contact, see get_contact().
1089    /// By default, key-contacts are listed.
1090    ///
1091    /// * `listflags` - A combination of flags:
1092    ///   - `DC_GCL_ADD_SELF` - Add SELF unless filtered by other parameters.
1093    ///   - `DC_GCL_ADDRESS` - List address-contacts instead of key-contacts.
1094    /// * `query` - A string to filter the list.
1095    pub async fn get_all(
1096        context: &Context,
1097        listflags: u32,
1098        query: Option<&str>,
1099    ) -> Result<Vec<ContactId>> {
1100        let self_addrs = context
1101            .get_all_self_addrs()
1102            .await?
1103            .into_iter()
1104            .collect::<HashSet<_>>();
1105        let mut add_self = false;
1106        let mut ret = Vec::new();
1107        let flag_add_self = (listflags & constants::DC_GCL_ADD_SELF) != 0;
1108        let flag_address = (listflags & constants::DC_GCL_ADDRESS) != 0;
1109        let minimal_origin = if context.get_config_bool(Config::Bot).await? {
1110            Origin::Unknown
1111        } else {
1112            Origin::IncomingReplyTo
1113        };
1114        if query.is_some() {
1115            let s3str_like_cmd = format!("%{}%", query.unwrap_or(""));
1116            context
1117                .sql
1118                .query_map(
1119                    "SELECT c.id, c.addr FROM contacts c
1120                 WHERE c.id>?
1121                 AND (c.fingerprint='')=?
1122                 AND c.origin>=? \
1123                 AND c.blocked=0 \
1124                 AND (iif(c.name='',c.authname,c.name) LIKE ? OR c.addr LIKE ?) \
1125                 ORDER BY c.last_seen DESC, c.id DESC;",
1126                    (
1127                        ContactId::LAST_SPECIAL,
1128                        flag_address,
1129                        minimal_origin,
1130                        &s3str_like_cmd,
1131                        &s3str_like_cmd,
1132                    ),
1133                    |row| {
1134                        let id: ContactId = row.get(0)?;
1135                        let addr: String = row.get(1)?;
1136                        Ok((id, addr))
1137                    },
1138                    |rows| {
1139                        for row in rows {
1140                            let (id, addr) = row?;
1141                            if !self_addrs.contains(&addr) {
1142                                ret.push(id);
1143                            }
1144                        }
1145                        Ok(())
1146                    },
1147                )
1148                .await?;
1149
1150            if let Some(query) = query {
1151                let self_addr = context
1152                    .get_config(Config::ConfiguredAddr)
1153                    .await?
1154                    .unwrap_or_default();
1155                let self_name = context
1156                    .get_config(Config::Displayname)
1157                    .await?
1158                    .unwrap_or_default();
1159                let self_name2 = stock_str::self_msg(context);
1160
1161                if self_addr.contains(query)
1162                    || self_name.contains(query)
1163                    || self_name2.await.contains(query)
1164                {
1165                    add_self = true;
1166                }
1167            } else {
1168                add_self = true;
1169            }
1170        } else {
1171            add_self = true;
1172
1173            context
1174                .sql
1175                .query_map(
1176                    "SELECT id, addr FROM contacts
1177                 WHERE id>?
1178                 AND (fingerprint='')=?
1179                 AND origin>=?
1180                 AND blocked=0
1181                 ORDER BY last_seen DESC, id DESC;",
1182                    (ContactId::LAST_SPECIAL, flag_address, minimal_origin),
1183                    |row| {
1184                        let id: ContactId = row.get(0)?;
1185                        let addr: String = row.get(1)?;
1186                        Ok((id, addr))
1187                    },
1188                    |rows| {
1189                        for row in rows {
1190                            let (id, addr) = row?;
1191                            if !self_addrs.contains(&addr) {
1192                                ret.push(id);
1193                            }
1194                        }
1195                        Ok(())
1196                    },
1197                )
1198                .await?;
1199        }
1200
1201        if flag_add_self && add_self {
1202            ret.push(ContactId::SELF);
1203        }
1204
1205        Ok(ret)
1206    }
1207
1208    /// Adds blocked mailinglists and broadcast channels as pseudo-contacts
1209    /// to allow unblocking them as if they are contacts
1210    /// (this way, only one unblock-ffi is needed and only one set of ui-functions,
1211    /// from the users perspective,
1212    /// there is not much difference in an email- and a mailinglist-address)
1213    async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1214        context
1215            .sql
1216            .transaction(move |transaction| {
1217                let mut stmt = transaction.prepare(
1218                    "SELECT name, grpid, type FROM chats WHERE (type=? OR type=?) AND blocked=?",
1219                )?;
1220                let rows = stmt.query_map(
1221                    (Chattype::Mailinglist, Chattype::InBroadcast, Blocked::Yes),
1222                    |row| {
1223                        let name: String = row.get(0)?;
1224                        let grpid: String = row.get(1)?;
1225                        let typ: Chattype = row.get(2)?;
1226                        Ok((name, grpid, typ))
1227                    },
1228                )?;
1229                let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1230                for (name, grpid, typ) in blocked_mailinglists {
1231                    let count = transaction.query_row(
1232                        "SELECT COUNT(id) FROM contacts WHERE addr=?",
1233                        [&grpid],
1234                        |row| {
1235                            let count: isize = row.get(0)?;
1236                            Ok(count)
1237                        },
1238                    )?;
1239                    if count == 0 {
1240                        transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1241                    }
1242
1243                    let fingerprint = if typ == Chattype::InBroadcast {
1244                        // Set some fingerprint so that is_pgp_contact() returns true,
1245                        // and the contact isn't marked with a letter icon.
1246                        "Blocked_broadcast"
1247                    } else {
1248                        ""
1249                    };
1250                    // Always do an update in case the blocking is reset or name is changed.
1251                    transaction.execute(
1252                        "UPDATE contacts SET name=?, origin=?, blocked=1, fingerprint=? WHERE addr=?",
1253                        (&name, Origin::MailinglistAddress, fingerprint, &grpid),
1254                    )?;
1255                }
1256                Ok(())
1257            })
1258            .await?;
1259        Ok(())
1260    }
1261
1262    /// Returns number of blocked contacts.
1263    pub async fn get_blocked_cnt(context: &Context) -> Result<usize> {
1264        let count = context
1265            .sql
1266            .count(
1267                "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
1268                (ContactId::LAST_SPECIAL,),
1269            )
1270            .await?;
1271        Ok(count)
1272    }
1273
1274    /// Get blocked contacts.
1275    pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1276        Contact::update_blocked_mailinglist_contacts(context)
1277            .await
1278            .context("cannot update blocked mailinglist contacts")?;
1279
1280        let list = context
1281            .sql
1282            .query_map_vec(
1283                "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1284                (ContactId::LAST_SPECIAL,),
1285                |row| {
1286                    let contact_id: ContactId = row.get(0)?;
1287                    Ok(contact_id)
1288                }
1289            )
1290            .await?;
1291        Ok(list)
1292    }
1293
1294    /// Returns a textual summary of the encryption state for the contact.
1295    ///
1296    /// This function returns a string explaining the encryption state
1297    /// of the contact and if the connection is encrypted the
1298    /// fingerprints of the keys involved.
1299    pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1300        ensure!(
1301            !contact_id.is_special(),
1302            "Can not provide encryption info for special contact"
1303        );
1304
1305        let contact = Contact::get_by_id(context, contact_id).await?;
1306        let addr = context
1307            .get_config(Config::ConfiguredAddr)
1308            .await?
1309            .unwrap_or_default();
1310
1311        let Some(fingerprint_other) = contact.fingerprint() else {
1312            return Ok(stock_str::encr_none(context).await);
1313        };
1314        let fingerprint_other = fingerprint_other.to_string();
1315
1316        let stock_message = if contact.public_key(context).await?.is_some() {
1317            stock_str::e2e_available(context).await
1318        } else {
1319            stock_str::encr_none(context).await
1320        };
1321
1322        let finger_prints = stock_str::finger_prints(context).await;
1323        let mut ret = format!("{stock_message}.\n{finger_prints}:");
1324
1325        let fingerprint_self = load_self_public_key(context)
1326            .await?
1327            .dc_fingerprint()
1328            .to_string();
1329        if addr < contact.addr {
1330            cat_fingerprint(
1331                &mut ret,
1332                &stock_str::self_msg(context).await,
1333                &addr,
1334                &fingerprint_self,
1335            );
1336            cat_fingerprint(
1337                &mut ret,
1338                contact.get_display_name(),
1339                &contact.addr,
1340                &fingerprint_other,
1341            );
1342        } else {
1343            cat_fingerprint(
1344                &mut ret,
1345                contact.get_display_name(),
1346                &contact.addr,
1347                &fingerprint_other,
1348            );
1349            cat_fingerprint(
1350                &mut ret,
1351                &stock_str::self_msg(context).await,
1352                &addr,
1353                &fingerprint_self,
1354            );
1355        }
1356
1357        Ok(ret)
1358    }
1359
1360    /// Delete a contact so that it disappears from the corresponding lists.
1361    /// Depending on whether there are ongoing chats, deletion is done by physical deletion or hiding.
1362    /// The contact is deleted from the local device.
1363    ///
1364    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
1365    pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1366        ensure!(!contact_id.is_special(), "Can not delete special contact");
1367
1368        context
1369            .sql
1370            .transaction(move |transaction| {
1371                // make sure, the transaction starts with a write command and becomes EXCLUSIVE by that -
1372                // upgrading later may be impossible by races.
1373                let deleted_contacts = transaction.execute(
1374                    "DELETE FROM contacts WHERE id=?
1375                     AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1376                    (contact_id, contact_id),
1377                )?;
1378                if deleted_contacts == 0 {
1379                    transaction.execute(
1380                        "UPDATE contacts SET origin=? WHERE id=?;",
1381                        (Origin::Hidden, contact_id),
1382                    )?;
1383                }
1384                Ok(())
1385            })
1386            .await?;
1387
1388        context.emit_event(EventType::ContactsChanged(None));
1389        Ok(())
1390    }
1391
1392    /// Updates `param` column in the database.
1393    pub async fn update_param(&self, context: &Context) -> Result<()> {
1394        context
1395            .sql
1396            .execute(
1397                "UPDATE contacts SET param=? WHERE id=?",
1398                (self.param.to_string(), self.id),
1399            )
1400            .await?;
1401        Ok(())
1402    }
1403
1404    /// Updates `status` column in the database.
1405    pub async fn update_status(&self, context: &Context) -> Result<()> {
1406        context
1407            .sql
1408            .execute(
1409                "UPDATE contacts SET status=? WHERE id=?",
1410                (&self.status, self.id),
1411            )
1412            .await?;
1413        Ok(())
1414    }
1415
1416    /// Get the ID of the contact.
1417    pub fn get_id(&self) -> ContactId {
1418        self.id
1419    }
1420
1421    /// Get email address. The email address is always set for a contact.
1422    pub fn get_addr(&self) -> &str {
1423        &self.addr
1424    }
1425
1426    /// Returns true if the contact is a key-contact.
1427    /// Otherwise it is an addresss-contact.
1428    pub fn is_key_contact(&self) -> bool {
1429        self.fingerprint.is_some()
1430    }
1431
1432    /// Returns OpenPGP fingerprint of a contact.
1433    ///
1434    /// `None` for address-contacts.
1435    pub fn fingerprint(&self) -> Option<Fingerprint> {
1436        if let Some(fingerprint) = &self.fingerprint {
1437            fingerprint.parse().ok()
1438        } else {
1439            None
1440        }
1441    }
1442
1443    /// Returns OpenPGP public key of a contact.
1444    ///
1445    /// Returns `None` if the contact is not a key-contact
1446    /// or if the key is not available.
1447    /// It is possible for a key-contact to not have a key,
1448    /// e.g. if only the fingerprint is known from a QR-code.
1449    pub async fn public_key(&self, context: &Context) -> Result<Option<SignedPublicKey>> {
1450        if self.id == ContactId::SELF {
1451            return Ok(Some(load_self_public_key(context).await?));
1452        }
1453
1454        if let Some(fingerprint) = &self.fingerprint {
1455            if let Some(public_key_bytes) = context
1456                .sql
1457                .query_row_optional(
1458                    "SELECT public_key
1459                     FROM public_keys
1460                     WHERE fingerprint=?",
1461                    (fingerprint,),
1462                    |row| {
1463                        let bytes: Vec<u8> = row.get(0)?;
1464                        Ok(bytes)
1465                    },
1466                )
1467                .await?
1468            {
1469                let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
1470                Ok(Some(public_key))
1471            } else {
1472                Ok(None)
1473            }
1474        } else {
1475            Ok(None)
1476        }
1477    }
1478
1479    /// Get name authorized by the contact.
1480    pub fn get_authname(&self) -> &str {
1481        &self.authname
1482    }
1483
1484    /// Get the contact name. This is the name as modified by the local user.
1485    /// May be an empty string.
1486    ///
1487    /// This name is typically used in a form where the user can edit the name of a contact.
1488    /// To get a fine name to display in lists etc., use `Contact::get_display_name` or `Contact::get_name_n_addr`.
1489    pub fn get_name(&self) -> &str {
1490        &self.name
1491    }
1492
1493    /// Get display name. This is the name as defined by the contact himself,
1494    /// modified by the user or, if both are unset, the email address.
1495    ///
1496    /// This name is typically used in lists.
1497    /// To get the name editable in a formular, use `Contact::get_name`.
1498    pub fn get_display_name(&self) -> &str {
1499        if !self.name.is_empty() {
1500            return &self.name;
1501        }
1502        if !self.authname.is_empty() {
1503            return &self.authname;
1504        }
1505        &self.addr
1506    }
1507
1508    /// Get a summary of name and address.
1509    ///
1510    /// The returned string is either "Name (email@domain.com)" or just
1511    /// "email@domain.com" if the name is unset.
1512    ///
1513    /// The result should only be used locally and never sent over the network
1514    /// as it leaks the local contact name.
1515    ///
1516    /// The summary is typically used when asking the user something about the contact.
1517    /// The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?"
1518    pub fn get_name_n_addr(&self) -> String {
1519        if !self.name.is_empty() {
1520            format!("{} ({})", self.name, self.addr)
1521        } else if !self.authname.is_empty() {
1522            format!("{} ({})", self.authname, self.addr)
1523        } else {
1524            (&self.addr).into()
1525        }
1526    }
1527
1528    /// Get the contact's profile image.
1529    /// This is the image set by each remote user on their own
1530    /// using set_config(context, "selfavatar", image).
1531    pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1532        self.get_profile_image_ex(context, true).await
1533    }
1534
1535    /// Get the contact's profile image.
1536    /// This is the image set by each remote user on their own
1537    /// using set_config(context, "selfavatar", image).
1538    async fn get_profile_image_ex(
1539        &self,
1540        context: &Context,
1541        show_fallback_icon: bool,
1542    ) -> Result<Option<PathBuf>> {
1543        if self.id == ContactId::SELF {
1544            if let Some(p) = context.get_config(Config::Selfavatar).await? {
1545                return Ok(Some(PathBuf::from(p))); // get_config() calls get_abs_path() internally already
1546            }
1547        } else if self.id == ContactId::DEVICE {
1548            return Ok(Some(chat::get_device_icon(context).await?));
1549        }
1550        if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
1551            return Ok(Some(chat::get_unencrypted_icon(context).await?));
1552        }
1553        if let Some(image_rel) = self.param.get(Param::ProfileImage)
1554            && !image_rel.is_empty()
1555        {
1556            return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1557        }
1558        Ok(None)
1559    }
1560
1561    /// Returns a color for the contact.
1562    /// For self-contact this returns gray if own keypair doesn't exist yet.
1563    /// See also [`self::get_color`].
1564    pub fn get_color(&self) -> u32 {
1565        get_color(self.id == ContactId::SELF, &self.addr, &self.fingerprint())
1566    }
1567
1568    /// Returns a color for the contact.
1569    /// Ensures that the color isn't gray. For self-contact this generates own keypair if it doesn't
1570    /// exist yet.
1571    /// See also [`self::get_color`].
1572    pub async fn get_or_gen_color(&self, context: &Context) -> Result<u32> {
1573        let mut fpr = self.fingerprint();
1574        if fpr.is_none() && self.id == ContactId::SELF {
1575            fpr = Some(load_self_public_key(context).await?.dc_fingerprint());
1576        }
1577        Ok(get_color(self.id == ContactId::SELF, &self.addr, &fpr))
1578    }
1579
1580    /// Gets the contact's status.
1581    ///
1582    /// Status is the last signature received in a message from this contact.
1583    pub fn get_status(&self) -> &str {
1584        self.status.as_str()
1585    }
1586
1587    /// Returns whether end-to-end encryption to the contact is available.
1588    pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1589        if self.id == ContactId::SELF {
1590            // We don't need to check if we have our own key.
1591            return Ok(true);
1592        }
1593        Ok(self.public_key(context).await?.is_some())
1594    }
1595
1596    /// Returns true if the contact
1597    /// can be added to verified chats.
1598    ///
1599    /// If contact is verified
1600    /// UI should display green checkmark after the contact name
1601    /// in contact list items and
1602    /// in chat member list items.
1603    ///
1604    /// In contact profile view, use this function only if there is no chat with the contact,
1605    /// otherwise use is_chat_protected().
1606    /// Use [Self::get_verifier_id] to display the verifier contact
1607    /// in the info section of the contact profile.
1608    pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1609        // We're always sort of secured-verified as we could verify the key on this device any time with the key
1610        // on this device
1611        if self.id == ContactId::SELF {
1612            return Ok(true);
1613        }
1614
1615        Ok(self.get_verifier_id(context).await?.is_some())
1616    }
1617
1618    /// Returns the `ContactId` that verified the contact.
1619    ///
1620    /// If this returns Some(_),
1621    /// display green checkmark in the profile and "Introduced by ..." line
1622    /// with the name and address of the contact
1623    /// formatted by [Self::get_name_n_addr].
1624    ///
1625    /// If this returns `Some(None)`, then the contact is verified,
1626    /// but it's unclear by whom.
1627    pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<Option<ContactId>>> {
1628        let verifier_id: u32 = context
1629            .sql
1630            .query_get_value("SELECT verifier FROM contacts WHERE id=?", (self.id,))
1631            .await?
1632            .with_context(|| format!("Contact {} does not exist", self.id))?;
1633
1634        if verifier_id == 0 {
1635            Ok(None)
1636        } else if verifier_id == self.id.to_u32() {
1637            Ok(Some(None))
1638        } else {
1639            Ok(Some(Some(ContactId::new(verifier_id))))
1640        }
1641    }
1642
1643    /// Returns the number of real (i.e. non-special) contacts in the database.
1644    pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1645        if !context.sql.is_open().await {
1646            return Ok(0);
1647        }
1648
1649        let count = context
1650            .sql
1651            .count(
1652                "SELECT COUNT(*) FROM contacts WHERE id>?;",
1653                (ContactId::LAST_SPECIAL,),
1654            )
1655            .await?;
1656        Ok(count)
1657    }
1658
1659    /// Returns true if a contact with this ID exists.
1660    pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1661        if contact_id.is_special() {
1662            return Ok(false);
1663        }
1664
1665        let exists = context
1666            .sql
1667            .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1668            .await?;
1669        Ok(exists)
1670    }
1671}
1672
1673/// Returns a color for a contact having given attributes.
1674///
1675/// The color is calculated from contact's fingerprint (for key-contacts) or email address (for
1676/// address-contacts; should be lowercased to avoid allocation) and can be used for an fallback
1677/// avatar with white initials as well as for headlines in bubbles of group chats.
1678pub fn get_color(is_self: bool, addr: &str, fingerprint: &Option<Fingerprint>) -> u32 {
1679    if let Some(fingerprint) = fingerprint {
1680        str_to_color(&fingerprint.hex())
1681    } else if is_self {
1682        0x808080
1683    } else {
1684        str_to_color(&to_lowercase(addr))
1685    }
1686}
1687
1688// Updates the names of the chats which use the contact name.
1689//
1690// This is one of the few duplicated data, however, getting the chat list is easier this way.
1691fn update_chat_names(
1692    context: &Context,
1693    transaction: &rusqlite::Connection,
1694    contact_id: ContactId,
1695) -> Result<()> {
1696    let chat_id: Option<ChatId> = transaction.query_row(
1697            "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1698            (Chattype::Single, contact_id),
1699            |row| {
1700                let chat_id: ChatId = row.get(0)?;
1701                Ok(chat_id)
1702            }
1703        ).optional()?;
1704
1705    if let Some(chat_id) = chat_id {
1706        let (addr, name, authname) = transaction.query_row(
1707            "SELECT addr, name, authname
1708                     FROM contacts
1709                     WHERE id=?",
1710            (contact_id,),
1711            |row| {
1712                let addr: String = row.get(0)?;
1713                let name: String = row.get(1)?;
1714                let authname: String = row.get(2)?;
1715                Ok((addr, name, authname))
1716            },
1717        )?;
1718
1719        let chat_name = if !name.is_empty() {
1720            name
1721        } else if !authname.is_empty() {
1722            authname
1723        } else {
1724            addr
1725        };
1726
1727        let count = transaction.execute(
1728            "UPDATE chats SET name=?1 WHERE id=?2 AND name!=?1",
1729            (chat_name, chat_id),
1730        )?;
1731
1732        if count > 0 {
1733            // Chat name updated
1734            context.emit_event(EventType::ChatModified(chat_id));
1735            chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1736        }
1737    }
1738
1739    Ok(())
1740}
1741
1742pub(crate) async fn set_blocked(
1743    context: &Context,
1744    sync: sync::Sync,
1745    contact_id: ContactId,
1746    new_blocking: bool,
1747) -> Result<()> {
1748    ensure!(
1749        !contact_id.is_special(),
1750        "Can't block special contact {contact_id}"
1751    );
1752    let contact = Contact::get_by_id(context, contact_id).await?;
1753
1754    if contact.blocked != new_blocking {
1755        context
1756            .sql
1757            .execute(
1758                "UPDATE contacts SET blocked=? WHERE id=?;",
1759                (i32::from(new_blocking), contact_id),
1760            )
1761            .await?;
1762
1763        // also (un)block all chats with _only_ this contact - we do not delete them to allow a
1764        // non-destructive blocking->unblocking.
1765        // (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
1766        // However, I'm not sure about this point; it may be confusing if the user wants to add other people;
1767        // this would result in recreating the same group...)
1768        if context
1769            .sql
1770            .execute(
1771                r#"
1772UPDATE chats
1773SET blocked=?
1774WHERE type=? AND id IN (
1775  SELECT chat_id FROM chats_contacts WHERE contact_id=?
1776);
1777"#,
1778                (new_blocking, Chattype::Single, contact_id),
1779            )
1780            .await
1781            .is_ok()
1782        {
1783            Contact::mark_noticed(context, contact_id).await?;
1784            context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1785        }
1786
1787        // also unblock mailinglist
1788        // if the contact is a mailinglist address explicitly created to allow unblocking
1789        if !new_blocking
1790            && contact.origin == Origin::MailinglistAddress
1791            && let Some((chat_id, ..)) = chat::get_chat_id_by_grpid(context, &contact.addr).await?
1792        {
1793            chat_id.unblock_ex(context, Nosync).await?;
1794        }
1795
1796        if sync.into() {
1797            let action = match new_blocking {
1798                true => chat::SyncAction::Block,
1799                false => chat::SyncAction::Unblock,
1800            };
1801            let sync_id = if let Some(fingerprint) = contact.fingerprint() {
1802                chat::SyncId::ContactFingerprint(fingerprint.hex())
1803            } else {
1804                chat::SyncId::ContactAddr(contact.addr.clone())
1805            };
1806
1807            chat::sync(context, sync_id, action)
1808                .await
1809                .log_err(context)
1810                .ok();
1811        }
1812    }
1813
1814    chatlist_events::emit_chatlist_changed(context);
1815    Ok(())
1816}
1817
1818/// Set profile image for a contact.
1819///
1820/// The given profile image is expected to be already in the blob directory
1821/// as profile images can be set only by receiving messages, this should be always the case, however.
1822///
1823/// For contact SELF, the image is not saved in the contact-database but as Config::Selfavatar.
1824pub(crate) async fn set_profile_image(
1825    context: &Context,
1826    contact_id: ContactId,
1827    profile_image: &AvatarAction,
1828) -> Result<()> {
1829    let mut contact = Contact::get_by_id(context, contact_id).await?;
1830    let changed = match profile_image {
1831        AvatarAction::Change(profile_image) => {
1832            if contact_id == ContactId::SELF {
1833                context
1834                    .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1835                    .await?;
1836            } else {
1837                contact.param.set(Param::ProfileImage, profile_image);
1838            }
1839            true
1840        }
1841        AvatarAction::Delete => {
1842            if contact_id == ContactId::SELF {
1843                context
1844                    .set_config_ex(Nosync, Config::Selfavatar, None)
1845                    .await?;
1846            } else {
1847                contact.param.remove(Param::ProfileImage);
1848            }
1849            true
1850        }
1851    };
1852    if changed {
1853        contact.update_param(context).await?;
1854        context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1855        chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1856    }
1857    Ok(())
1858}
1859
1860/// Sets contact status.
1861///
1862/// For contact SELF, the status is not saved in the contact table, but as Config::Selfstatus.
1863pub(crate) async fn set_status(
1864    context: &Context,
1865    contact_id: ContactId,
1866    status: String,
1867) -> Result<()> {
1868    if contact_id == ContactId::SELF {
1869        context
1870            .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1871            .await?;
1872    } else {
1873        let mut contact = Contact::get_by_id(context, contact_id).await?;
1874
1875        if contact.status != status {
1876            contact.status = status;
1877            contact.update_status(context).await?;
1878            context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1879        }
1880    }
1881    Ok(())
1882}
1883
1884/// Updates last seen timestamp of the contact if it is earlier than the given `timestamp`.
1885pub(crate) async fn update_last_seen(
1886    context: &Context,
1887    contact_id: ContactId,
1888    timestamp: i64,
1889) -> Result<()> {
1890    ensure!(
1891        !contact_id.is_special(),
1892        "Can not update special contact last seen timestamp"
1893    );
1894
1895    if context
1896        .sql
1897        .execute(
1898            "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1899            (timestamp, contact_id),
1900        )
1901        .await?
1902        > 0
1903        && timestamp > time() - SEEN_RECENTLY_SECONDS
1904    {
1905        context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1906        context
1907            .scheduler
1908            .interrupt_recently_seen(contact_id, timestamp)
1909            .await;
1910    }
1911    Ok(())
1912}
1913
1914/// Marks contact `contact_id` as verified by `verifier_id`.
1915///
1916/// `verifier_id == None` means that the verifier is unknown.
1917pub(crate) async fn mark_contact_id_as_verified(
1918    context: &Context,
1919    contact_id: ContactId,
1920    verifier_id: Option<ContactId>,
1921) -> Result<()> {
1922    ensure_and_debug_assert_ne!(contact_id, ContactId::SELF,);
1923    ensure_and_debug_assert_ne!(
1924        Some(contact_id),
1925        verifier_id,
1926        "Contact cannot be verified by self",
1927    );
1928    let by_self = verifier_id == Some(ContactId::SELF);
1929    let mut verifier_id = verifier_id.unwrap_or(contact_id);
1930    context
1931        .sql
1932        .transaction(|transaction| {
1933            let contact_fingerprint: String = transaction.query_row(
1934                "SELECT fingerprint FROM contacts WHERE id=?",
1935                (contact_id,),
1936                |row| row.get(0),
1937            )?;
1938            if contact_fingerprint.is_empty() {
1939                bail!("Non-key-contact {contact_id} cannot be verified");
1940            }
1941            if verifier_id != ContactId::SELF {
1942                let (verifier_fingerprint, verifier_verifier_id): (String, ContactId) = transaction
1943                    .query_row(
1944                        "SELECT fingerprint, verifier FROM contacts WHERE id=?",
1945                        (verifier_id,),
1946                        |row| Ok((row.get(0)?, row.get(1)?)),
1947                    )?;
1948                if verifier_fingerprint.is_empty() {
1949                    bail!(
1950                        "Contact {contact_id} cannot be verified by non-key-contact {verifier_id}"
1951                    );
1952                }
1953                ensure!(
1954                    verifier_id == contact_id || verifier_verifier_id != ContactId::UNDEFINED,
1955                    "Contact {contact_id} cannot be verified by unverified contact {verifier_id}",
1956                );
1957                if verifier_verifier_id == verifier_id {
1958                    // Avoid introducing incorrect reverse chains: if the verifier itself has an
1959                    // unknown verifier, it may be `contact_id` actually (directly or indirectly) on
1960                    // the other device (which is needed for getting "verified by unknown contact"
1961                    // in the first place).
1962                    verifier_id = contact_id;
1963                }
1964            }
1965            transaction.execute(
1966                "UPDATE contacts SET verifier=?1
1967                 WHERE id=?2 AND (verifier=0 OR verifier=id OR ?3)",
1968                (verifier_id, contact_id, by_self),
1969            )?;
1970            Ok(())
1971        })
1972        .await?;
1973    Ok(())
1974}
1975
1976fn cat_fingerprint(ret: &mut String, name: &str, addr: &str, fingerprint: &str) {
1977    *ret += &format!("\n\n{name} ({addr}):\n{fingerprint}");
1978}
1979
1980fn split_address_book(book: &str) -> Vec<(&str, &str)> {
1981    book.lines()
1982        .collect::<Vec<&str>>()
1983        .chunks(2)
1984        .filter_map(|chunk| {
1985            let name = chunk.first()?;
1986            let addr = chunk.get(1)?;
1987            Some((*name, *addr))
1988        })
1989        .collect()
1990}
1991
1992#[derive(Debug)]
1993pub(crate) struct RecentlySeenInterrupt {
1994    contact_id: ContactId,
1995    timestamp: i64,
1996}
1997
1998#[derive(Debug)]
1999pub(crate) struct RecentlySeenLoop {
2000    /// Task running "recently seen" loop.
2001    handle: task::JoinHandle<()>,
2002
2003    interrupt_send: Sender<RecentlySeenInterrupt>,
2004}
2005
2006impl RecentlySeenLoop {
2007    pub(crate) fn new(context: Context) -> Self {
2008        let (interrupt_send, interrupt_recv) = channel::bounded(1);
2009
2010        let handle = task::spawn(Self::run(context, interrupt_recv));
2011        Self {
2012            handle,
2013            interrupt_send,
2014        }
2015    }
2016
2017    async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
2018        type MyHeapElem = (Reverse<i64>, ContactId);
2019
2020        let now = SystemTime::now();
2021        let now_ts = now
2022            .duration_since(SystemTime::UNIX_EPOCH)
2023            .unwrap_or_default()
2024            .as_secs() as i64;
2025
2026        // Priority contains all recently seen sorted by the timestamp
2027        // when they become not recently seen.
2028        //
2029        // Initialize with contacts which are currently seen, but will
2030        // become unseen in the future.
2031        let mut unseen_queue: BinaryHeap<MyHeapElem> = context
2032            .sql
2033            .query_map_collect(
2034                "SELECT id, last_seen FROM contacts
2035                 WHERE last_seen > ?",
2036                (now_ts - SEEN_RECENTLY_SECONDS,),
2037                |row| {
2038                    let contact_id: ContactId = row.get("id")?;
2039                    let last_seen: i64 = row.get("last_seen")?;
2040                    Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
2041                },
2042            )
2043            .await
2044            .unwrap_or_default();
2045
2046        loop {
2047            let now = SystemTime::now();
2048            let (until, contact_id) =
2049                if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
2050                    (
2051                        UNIX_EPOCH
2052                            + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
2053                            + Duration::from_secs(1),
2054                        Some(contact_id),
2055                    )
2056                } else {
2057                    // Sleep for 24 hours.
2058                    (now + Duration::from_secs(86400), None)
2059                };
2060
2061            if let Ok(duration) = until.duration_since(now) {
2062                info!(
2063                    context,
2064                    "Recently seen loop waiting for {} or interrupt",
2065                    duration_to_str(duration)
2066                );
2067
2068                match timeout(duration, interrupt.recv()).await {
2069                    Err(_) => {
2070                        // Timeout, notify about contact.
2071                        if let Some(contact_id) = contact_id {
2072                            context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2073                            chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2074                                &context,
2075                                *contact_id,
2076                            )
2077                            .await;
2078                            unseen_queue.pop();
2079                        }
2080                    }
2081                    Ok(Err(err)) => {
2082                        warn!(
2083                            context,
2084                            "Error receiving an interruption in recently seen loop: {}", err
2085                        );
2086                        // Maybe the sender side is closed.
2087                        // Terminate the loop to avoid looping indefinitely.
2088                        return;
2089                    }
2090                    Ok(Ok(RecentlySeenInterrupt {
2091                        contact_id,
2092                        timestamp,
2093                    })) => {
2094                        // Received an interrupt.
2095                        if contact_id != ContactId::UNDEFINED {
2096                            unseen_queue
2097                                .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2098                        }
2099                    }
2100                }
2101            } else {
2102                info!(
2103                    context,
2104                    "Recently seen loop is not waiting, event is already due."
2105                );
2106
2107                // Event is already in the past.
2108                if let Some(contact_id) = contact_id {
2109                    context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2110                    chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2111                        &context,
2112                        *contact_id,
2113                    )
2114                    .await;
2115                }
2116                unseen_queue.pop();
2117            }
2118        }
2119    }
2120
2121    pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2122        self.interrupt_send
2123            .try_send(RecentlySeenInterrupt {
2124                contact_id,
2125                timestamp,
2126            })
2127            .ok();
2128    }
2129
2130    #[cfg(test)]
2131    pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2132        self.interrupt_send
2133            .send(RecentlySeenInterrupt {
2134                contact_id,
2135                timestamp,
2136            })
2137            .await
2138            .unwrap();
2139    }
2140
2141    pub(crate) async fn abort(self) {
2142        self.handle.abort();
2143
2144        // Await aborted task to ensure the `Future` is dropped
2145        // with all resources moved inside such as the `Context`
2146        // reference to `InnerContext`.
2147        self.handle.await.ok();
2148    }
2149}
2150
2151#[cfg(test)]
2152mod contact_tests;