libsignal_service/
models.rs

1use crate::utils::serde_optional_e164;
2use bytes::Bytes;
3use libsignal_core::E164;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6use uuid::Uuid;
7
8/// Attachment represents an attachment received from a peer
9#[derive(Debug, Serialize, Deserialize)]
10pub struct Attachment<R> {
11    pub content_type: String,
12    pub reader: R,
13}
14
15/// Mirror of the protobuf ContactDetails message
16/// but with stronger types (e.g. `ServiceAddress` instead of optional uuid and string phone numbers)
17/// and some helper functions
18#[derive(Debug, Serialize, Deserialize)]
19pub struct Contact {
20    pub uuid: Uuid,
21    #[serde(with = "serde_optional_e164")]
22    pub phone_number: Option<E164>,
23    pub name: String,
24    pub expire_timer: u32,
25    pub expire_timer_version: u32,
26    pub inbox_position: u32,
27    #[serde(skip)]
28    pub avatar: Option<Attachment<Bytes>>,
29}
30
31#[derive(Error, Debug)]
32pub enum ParseContactError {
33    #[error(transparent)]
34    Protobuf(#[from] prost::DecodeError),
35    #[error(transparent)]
36    Uuid(#[from] uuid::Error),
37    #[error("missing UUID")]
38    MissingUuid,
39    #[error("missing profile key")]
40    MissingProfileKey,
41    #[error("missing avatar content-type")]
42    MissingAvatarContentType,
43}
44
45impl Contact {
46    pub fn from_proto(
47        contact_details: crate::proto::ContactDetails,
48        avatar_data: Option<Bytes>,
49    ) -> Result<Self, ParseContactError> {
50        Ok(Self {
51            uuid: contact_details
52                .aci
53                .as_ref()
54                .ok_or(ParseContactError::MissingUuid)?
55                .parse()?,
56            phone_number: contact_details.number.as_ref().and_then(|n| {
57                n.parse::<E164>()
58                    .inspect_err(|e| {
59                        tracing::warn!(e=%e, "unparseable phone number");
60                    })
61                    .ok()
62            }),
63            name: contact_details.name().into(),
64            expire_timer: contact_details.expire_timer(),
65            expire_timer_version: contact_details.expire_timer_version(),
66            inbox_position: contact_details.inbox_position(),
67            avatar: contact_details.avatar.and_then(|avatar| {
68                if let (Some(content_type), Some(avatar_data)) =
69                    (avatar.content_type, avatar_data)
70                {
71                    Some(Attachment {
72                        content_type,
73                        reader: avatar_data,
74                    })
75                } else {
76                    tracing::warn!("missing avatar content-type, skipping.");
77                    None
78                }
79            }),
80        })
81    }
82}