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    pub terminated: bool,
163}
164
165#[derive(Debug, Clone)]
166pub struct GroupChanges {
167    pub group_id: GroupV2Id,
168    pub editor: Aci,
169    pub version: u32,
170    pub changes: Vec<GroupChange>,
171    pub change_epoch: u32,
172}
173
174#[derive(derive_more::Debug, Clone)]
175pub enum GroupChange {
176    NewMember(Member),
177    DeleteMember(Aci),
178    ModifyMemberRole {
179        aci: Aci,
180        role: Role,
181    },
182    ModifyMemberProfileKey {
183        aci: Aci,
184        #[debug(ignore)]
185        profile_key: ProfileKey,
186    },
187    NewPendingMember(PendingMember),
188    DeletePendingMember(ServiceId),
189    PromotePendingMember {
190        address: ServiceId,
191        #[debug(ignore)]
192        profile_key: ProfileKey,
193    },
194    Title(String),
195    Avatar(String),
196    Timer(Option<Timer>),
197    AttributeAccess(AccessRequired),
198    MemberAccess(AccessRequired),
199    InviteLinkAccess(AccessRequired),
200    NewRequestingMember(RequestingMember),
201    DeleteRequestingMember(Aci),
202    PromoteRequestingMember {
203        aci: Aci,
204        role: Role,
205    },
206    InviteLinkPassword(String),
207    Description(Option<String>),
208    AnnouncementOnly(bool),
209    AddBannedMember(BannedMember),
210    DeleteBannedMember(ServiceId),
211    PromotePendingPniAciMemberProfileKey(PromotedMember),
212    MemberLabel {
213        user_id: ServiceId,
214        label_emoji: Option<String>,
215        label_string: Option<String>,
216    },
217    MemberLabelAccess(AccessRequired),
218    TerminateGroup,
219}
220
221#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
222pub struct Timer {
223    pub duration: u32,
224}
225
226// Conversion from and to protobuf definitions
227
228impl TryFrom<i32> for Role {
229    type Error = GroupDecodingError;
230
231    fn try_from(value: i32) -> Result<Self, Self::Error> {
232        use crate::proto::member::Role as ProtoRole;
233        match crate::proto::member::Role::try_from(value) {
234            Ok(ProtoRole::Unknown) => Ok(Role::Unknown),
235            Ok(ProtoRole::Default) => Ok(Role::Default),
236            Ok(ProtoRole::Administrator) => Ok(Role::Administrator),
237            Err(_e) => Err(GroupDecodingError::WrongEnumValue),
238        }
239    }
240}
241
242impl From<Role> for i32 {
243    fn from(val: Role) -> Self {
244        use crate::proto::member::Role::*;
245        match val {
246            Role::Unknown => Unknown,
247            Role::Default => Default,
248            Role::Administrator => Administrator,
249        }
250        .into()
251    }
252}
253
254impl TryFrom<i32> for AccessRequired {
255    type Error = GroupDecodingError;
256
257    fn try_from(value: i32) -> Result<Self, Self::Error> {
258        use crate::proto::access_control::AccessRequired as ProtoAccessRequired;
259        match crate::proto::access_control::AccessRequired::try_from(value) {
260            Ok(ProtoAccessRequired::Unknown) => Ok(AccessRequired::Unknown),
261            Ok(ProtoAccessRequired::Any) => Ok(AccessRequired::Any),
262            Ok(ProtoAccessRequired::Member) => Ok(AccessRequired::Member),
263            Ok(ProtoAccessRequired::Administrator) => {
264                Ok(AccessRequired::Administrator)
265            },
266            Ok(ProtoAccessRequired::Unsatisfiable) => {
267                Ok(AccessRequired::Unsatisfiable)
268            },
269            Err(_e) => Err(GroupDecodingError::WrongEnumValue),
270        }
271    }
272}
273
274impl From<AccessRequired> for i32 {
275    fn from(val: AccessRequired) -> Self {
276        use crate::proto::access_control::AccessRequired::*;
277        match val {
278            AccessRequired::Unknown => Unknown,
279            AccessRequired::Any => Any,
280            AccessRequired::Member => Member,
281            AccessRequired::Administrator => Administrator,
282            AccessRequired::Unsatisfiable => Unsatisfiable,
283        }
284        .into()
285    }
286}
287
288impl TryFrom<crate::proto::AccessControl> for AccessControl {
289    type Error = GroupDecodingError;
290
291    fn try_from(
292        value: crate::proto::AccessControl,
293    ) -> Result<Self, Self::Error> {
294        Ok(Self {
295            attributes: value.attributes.try_into()?,
296            members: value.members.try_into()?,
297            add_from_invite_link: value.add_from_invite_link.try_into()?,
298            member_label: value.member_label.try_into()?,
299        })
300    }
301}