libsignal_service/
models.rs

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