Skip to main content

libsignal_service/groups_v2/
model.rs

1use std::{convert::TryFrom, convert::TryInto};
2
3use libsignal_protocol::{Aci, Pni, ServiceId};
4use serde::{Deserialize, Serialize};
5use zkgroup::profiles::{ExpiringProfileKeyCredential, ProfileKey};
6
7use crate::sender::GroupV2Id;
8
9use super::GroupDecodingError;
10
11/// A candidate member for group creation/modification.
12///
13/// If credential is Some, member will be added with a presentation.
14/// If credential is None, member will be added as pending (invite).
15#[derive(Clone)]
16pub struct GroupMemberCandidate {
17    pub service_id: ServiceId,
18    pub credential: Option<ExpiringProfileKeyCredential>,
19}
20
21impl std::fmt::Debug for GroupMemberCandidate {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        f.debug_struct("GroupCandidate")
24            .field("service_id", &self.service_id)
25            .field(
26                "credential",
27                &self.credential.as_ref().map(|_| "[credential]"),
28            )
29            .finish()
30    }
31}
32
33#[derive(Copy, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub enum Role {
35    Unknown,
36    Default,
37    Administrator,
38}
39
40#[derive(derive_more::Debug, Clone, Deserialize, Serialize)]
41pub struct Member {
42    #[serde(with = "aci_serde")]
43    pub aci: Aci,
44    pub role: Role,
45    #[debug(ignore)]
46    pub profile_key: ProfileKey,
47    pub joined_at_version: u32,
48    pub label: Option<String>,
49    pub label_emoji: Option<String>,
50}
51
52impl PartialEq for Member {
53    fn eq(&self, other: &Self) -> bool {
54        self.aci == other.aci
55    }
56}
57
58mod aci_serde {
59    use super::*;
60    use serde::{Deserializer, Serializer};
61
62    pub fn serialize<S>(p: &Aci, s: S) -> Result<S::Ok, S::Error>
63    where
64        S: Serializer,
65    {
66        s.serialize_str(&p.service_id_string())
67    }
68
69    pub fn deserialize<'de, D>(d: D) -> Result<Aci, D::Error>
70    where
71        D: Deserializer<'de>,
72    {
73        // We have to go through String deserialization,
74        // because Aci does not implement Deserialize (duh).
75        let s = std::borrow::Cow::<str>::deserialize(d)?;
76        match Aci::parse_from_service_id_string(&s) {
77            Some(aci) => Ok(aci),
78            None => Err(serde::de::Error::custom("Invalid ACI string")),
79        }
80    }
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
84pub struct PendingMember {
85    pub address: ServiceId,
86    pub role: Role,
87    pub added_by_aci: Aci,
88    pub timestamp: u64,
89}
90
91#[derive(derive_more::Debug, Clone)]
92pub struct RequestingMember {
93    pub aci: Aci,
94    #[debug(ignore)]
95    pub profile_key: ProfileKey,
96    pub timestamp: u64,
97}
98
99impl PartialEq for RequestingMember {
100    fn eq(&self, other: &Self) -> bool {
101        self.aci == other.aci
102    }
103}
104
105#[derive(Debug, Clone)]
106pub struct BannedMember {
107    pub user_id: ServiceId,
108    pub timestamp: u64,
109}
110
111impl PartialEq for BannedMember {
112    fn eq(&self, other: &Self) -> bool {
113        self.user_id == other.user_id
114    }
115}
116
117#[derive(derive_more::Debug, Clone)]
118pub struct PromotedMember {
119    pub aci: Aci,
120    pub pni: Pni,
121    #[debug(ignore)]
122    pub profile_key: ProfileKey,
123}
124
125impl PartialEq for PromotedMember {
126    fn eq(&self, other: &Self) -> bool {
127        self.aci == other.aci && self.pni == other.pni
128    }
129}
130
131#[derive(Copy, Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub enum AccessRequired {
133    Unknown,
134    Any,
135    Member,
136    Administrator,
137    Unsatisfiable,
138}
139
140#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
141pub struct AccessControl {
142    pub attributes: AccessRequired,
143    pub members: AccessRequired,
144    pub add_from_invite_link: AccessRequired,
145    pub member_label: AccessRequired,
146}
147
148#[derive(Debug, Clone, PartialEq)]
149pub struct Group {
150    pub title: String,
151    pub avatar: String,
152    pub disappearing_messages_timer: Option<Timer>,
153    pub access_control: Option<AccessControl>,
154    pub version: u32,
155    pub members: Vec<Member>,
156    pub members_pending_profile_key: Vec<PendingMember>,
157    pub members_pending_admin_approval: Vec<RequestingMember>,
158    pub invite_link_password: Vec<u8>,
159    pub description_text: Option<String>,
160    pub announcements_only: bool,
161    pub members_banned: Vec<BannedMember>,
162}
163
164#[derive(Debug, Clone)]
165pub struct GroupChanges {
166    pub group_id: GroupV2Id,
167    pub editor: Aci,
168    pub version: u32,
169    pub changes: Vec<GroupChange>,
170    pub change_epoch: u32,
171}
172
173#[derive(derive_more::Debug, Clone)]
174pub enum GroupChange {
175    NewMember(Member),
176    DeleteMember(Aci),
177    ModifyMemberRole {
178        aci: Aci,
179        role: Role,
180    },
181    ModifyMemberProfileKey {
182        aci: Aci,
183        #[debug(ignore)]
184        profile_key: ProfileKey,
185    },
186    NewPendingMember(PendingMember),
187    DeletePendingMember(ServiceId),
188    PromotePendingMember {
189        address: ServiceId,
190        #[debug(ignore)]
191        profile_key: ProfileKey,
192    },
193    Title(String),
194    Avatar(String),
195    Timer(Option<Timer>),
196    AttributeAccess(AccessRequired),
197    MemberAccess(AccessRequired),
198    InviteLinkAccess(AccessRequired),
199    NewRequestingMember(RequestingMember),
200    DeleteRequestingMember(Aci),
201    PromoteRequestingMember {
202        aci: Aci,
203        role: Role,
204    },
205    InviteLinkPassword(String),
206    Description(Option<String>),
207    AnnouncementOnly(bool),
208    AddBannedMember(BannedMember),
209    DeleteBannedMember(ServiceId),
210    PromotePendingPniAciMemberProfileKey(PromotedMember),
211    MemberLabel {
212        user_id: ServiceId,
213        label_emoji: Option<String>,
214        label_string: Option<String>,
215    },
216    MemberLabelAccess(AccessRequired),
217}
218
219#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
220pub struct Timer {
221    pub duration: u32,
222}
223
224// Conversion from and to protobuf definitions
225
226impl TryFrom<i32> for Role {
227    type Error = GroupDecodingError;
228
229    fn try_from(value: i32) -> Result<Self, Self::Error> {
230        use crate::proto::member::Role as ProtoRole;
231        match crate::proto::member::Role::try_from(value) {
232            Ok(ProtoRole::Unknown) => Ok(Role::Unknown),
233            Ok(ProtoRole::Default) => Ok(Role::Default),
234            Ok(ProtoRole::Administrator) => Ok(Role::Administrator),
235            Err(_e) => Err(GroupDecodingError::WrongEnumValue),
236        }
237    }
238}
239
240impl From<Role> for i32 {
241    fn from(val: Role) -> Self {
242        use crate::proto::member::Role::*;
243        match val {
244            Role::Unknown => Unknown,
245            Role::Default => Default,
246            Role::Administrator => Administrator,
247        }
248        .into()
249    }
250}
251
252impl TryFrom<i32> for AccessRequired {
253    type Error = GroupDecodingError;
254
255    fn try_from(value: i32) -> Result<Self, Self::Error> {
256        use crate::proto::access_control::AccessRequired as ProtoAccessRequired;
257        match crate::proto::access_control::AccessRequired::try_from(value) {
258            Ok(ProtoAccessRequired::Unknown) => Ok(AccessRequired::Unknown),
259            Ok(ProtoAccessRequired::Any) => Ok(AccessRequired::Any),
260            Ok(ProtoAccessRequired::Member) => Ok(AccessRequired::Member),
261            Ok(ProtoAccessRequired::Administrator) => {
262                Ok(AccessRequired::Administrator)
263            },
264            Ok(ProtoAccessRequired::Unsatisfiable) => {
265                Ok(AccessRequired::Unsatisfiable)
266            },
267            Err(_e) => Err(GroupDecodingError::WrongEnumValue),
268        }
269    }
270}
271
272impl From<AccessRequired> for i32 {
273    fn from(val: AccessRequired) -> Self {
274        use crate::proto::access_control::AccessRequired::*;
275        match val {
276            AccessRequired::Unknown => Unknown,
277            AccessRequired::Any => Any,
278            AccessRequired::Member => Member,
279            AccessRequired::Administrator => Administrator,
280            AccessRequired::Unsatisfiable => Unsatisfiable,
281        }
282        .into()
283    }
284}
285
286impl TryFrom<crate::proto::AccessControl> for AccessControl {
287    type Error = GroupDecodingError;
288
289    fn try_from(
290        value: crate::proto::AccessControl,
291    ) -> Result<Self, Self::Error> {
292        Ok(Self {
293            attributes: value.attributes.try_into()?,
294            members: value.members.try_into()?,
295            add_from_invite_link: value.add_from_invite_link.try_into()?,
296            member_label: value.member_label.try_into()?,
297        })
298    }
299}