diff --git a/rust/message-backup/src/backup/chat.rs b/rust/message-backup/src/backup/chat.rs index f1149f4a..0706f5d0 100644 --- a/rust/message-backup/src/backup/chat.rs +++ b/rust/message-backup/src/backup/chat.rs @@ -69,6 +69,8 @@ pub enum ChatError { NoRecipient(RecipientId), /// cannot have a chat with recipient {0:?}, a {1:?} InvalidRecipient(RecipientId, DestinationKind), + /// chat with {0:?} has an expirationTimerMs but no expireTimerVersion + MissingExpireTimerVersion(RecipientId), /// chat item: {0} ChatItem(#[from] ChatItemError), /// {0:?} already appeared @@ -172,6 +174,7 @@ pub struct ChatData { #[serde(bound(serialize = "M::List>: serde::Serialize"))] pub items: M::List>, pub expiration_timer: Option, + pub expiration_timer_version: u32, pub mute_until: Option, pub style: Option>, pub pinned_order: Option, @@ -349,6 +352,7 @@ impl< id: _, recipientId, expirationTimerMs, + expireTimerVersion, muteUntilMs, pinnedOrder, archived, @@ -389,9 +393,15 @@ impl< let mute_until = NonZeroU64::new(muteUntilMs) .map(|t| Timestamp::from_millis(t.get(), "Chat.muteUntilMs")); + if expiration_timer.is_some() && expireTimerVersion == 0 { + return Err(ChatError::MissingExpireTimerVersion(recipient_id)); + } + let expiration_timer_version = expireTimerVersion; + Ok(Self { recipient, expiration_timer, + expiration_timer_version, mute_until, items: Default::default(), style, @@ -869,6 +879,7 @@ mod test { recipient: TestContext::test_recipient().clone(), items: Vec::default(), expiration_timer: None, + expiration_timer_version: 0, mute_until: None, style: None, pinned_order: None, @@ -879,7 +890,12 @@ mod test { ); } - #[test_case(|x| x.expirationTimerMs = 123456 => Ok(()); "with_expiration_timer")] + #[test_case(|x| { + x.expirationTimerMs = 123456; + x.expireTimerVersion = 3; + } => Ok(()); "with_expiration_timer")] + #[test_case(|x| x.expirationTimerMs = 123456 => Err(ChatError::MissingExpireTimerVersion(TestContext::SELF_ID)); "with_expiration_timer_only")] + #[test_case(|x| x.expireTimerVersion = 3 => Ok(()); "with_expire_timer_version_only")] #[test_case(|x| x.muteUntilMs = MillisecondsSinceEpoch::TEST_VALUE.0 => Ok(()); "with mute until")] #[test_case( |x| x.pinnedOrder = TestContext::DUPLICATE_PINNED_ORDER.0.get() => diff --git a/rust/message-backup/src/backup/recipient/group/members.rs b/rust/message-backup/src/backup/recipient/group/members.rs index 2c05b31c..a24d73d9 100644 --- a/rust/message-backup/src/backup/recipient/group/members.rs +++ b/rust/message-backup/src/backup/recipient/group/members.rs @@ -4,7 +4,6 @@ // use libsignal_core::{Aci, ServiceId, WrongKindOfServiceIdError}; -use zkgroup::ProfileKeyBytes; use super::GroupError; use crate::backup::serialize::{self, SerializeOrder}; @@ -23,8 +22,6 @@ pub struct GroupMember { #[serde(serialize_with = "serialize::service_id_as_string")] pub user_id: Aci, pub role: Role, - #[serde(with = "hex")] - pub profile_key: ProfileKeyBytes, pub joined_at_version: u32, pub(super) _limit_construction_to_module: (), } @@ -42,7 +39,6 @@ impl TryFrom for GroupMember { let proto::group::Member { userId, role, - profileKey, joinedAtVersion, special_fields: _, } = value; @@ -61,14 +57,11 @@ impl TryFrom for GroupMember { proto::group::member::Role::DEFAULT => Role::Default, proto::group::member::Role::ADMINISTRATOR => Role::Administrator, }; - let profile_key = ProfileKeyBytes::try_from(profileKey) - .map_err(|_| GroupError::MemberInvalidProfileKey)?; let joined_at_version = joinedAtVersion; Ok(GroupMember { user_id, role, - profile_key, joined_at_version, _limit_construction_to_module: (), }) @@ -108,7 +101,6 @@ impl TryFrom for GroupMemberPendingProfil let proto::group::Member { userId, role, - profileKey, joinedAtVersion, special_fields: _, } = member @@ -125,9 +117,6 @@ impl TryFrom for GroupMemberPendingProfil proto::group::member::Role::DEFAULT => Role::Default, proto::group::member::Role::ADMINISTRATOR => Role::Administrator, }; - if !profileKey.is_empty() { - return Err(GroupError::MemberPendingProfileKeyHasProfileKey); - } let joined_at_version = joinedAtVersion; let added_by_user_id = ServiceId::parse_from_service_id_binary(&addedByUserId) @@ -162,8 +151,6 @@ impl TryFrom for GroupMemberPendingProfil pub struct GroupMemberPendingAdminApproval { #[serde(serialize_with = "serialize::service_id_as_string")] pub user_id: Aci, - #[serde(with = "hex")] - pub profile_key: ProfileKeyBytes, pub timestamp: Timestamp, pub(super) _limit_construction_to_module: (), } @@ -180,7 +167,6 @@ impl TryFrom for GroupMemberPendingAdm fn try_from(member: proto::group::MemberPendingAdminApproval) -> Result { let proto::group::MemberPendingAdminApproval { userId, - profileKey, timestamp, special_fields: _, } = member; @@ -196,13 +182,10 @@ impl TryFrom for GroupMemberPendingAdm found: e.actual, }, )?; - let profile_key = ProfileKeyBytes::try_from(profileKey) - .map_err(|_| GroupError::MemberInvalidProfileKey)?; let timestamp = Timestamp::from_millis(timestamp, "MemberPendingAdminApproval"); Ok(GroupMemberPendingAdminApproval { user_id, - profile_key, timestamp, _limit_construction_to_module: (), }) @@ -262,7 +245,6 @@ mod tests { Self { userId: proto::Contact::TEST_ACI.to_vec(), role: proto::group::member::Role::DEFAULT.into(), - profileKey: proto::Contact::TEST_PROFILE_KEY.to_vec(), joinedAtVersion: 1, ..Default::default() } @@ -274,7 +256,6 @@ mod tests { Self { user_id: Aci::from_uuid_bytes(proto::Contact::TEST_ACI), role: Role::Default, - profile_key: proto::Contact::TEST_PROFILE_KEY, joined_at_version: 1, _limit_construction_to_module: (), } @@ -293,7 +274,6 @@ mod tests { #[test_case(|x| x.userId = vec![] => Err(GroupError::MemberInvalidServiceId { which: "member" }); "empty userId")] #[test_case(|x| x.role = proto::group::member::Role::ADMINISTRATOR.into() => Ok(()); "administrator")] #[test_case(|x| x.role = proto::group::member::Role::UNKNOWN.into() => Err(GroupError::MemberRoleUnknown); "role unknown")] - #[test_case(|x| x.profileKey = vec![] => Err(GroupError::MemberInvalidProfileKey); "empty profileKey")] fn member(modifier: impl FnOnce(&mut proto::group::Member)) -> Result<(), GroupError> { let mut member = proto::group::Member::test_data(); modifier(&mut member); @@ -305,11 +285,7 @@ mod tests { pub(crate) fn test_data() -> Self { Self { - member: Some(proto::group::Member { - profileKey: vec![], - ..proto::group::Member::test_data() - }) - .into(), + member: Some(proto::group::Member::test_data()).into(), timestamp: MillisecondsSinceEpoch::TEST_VALUE.0, addedByUserId: Self::INVITER_ACI.to_vec(), ..Default::default() @@ -348,7 +324,6 @@ mod tests { #[test_case(|x| x.member.as_mut().unwrap().userId = vec![] => Err(GroupError::MemberInvalidServiceId { which: "invited member" }); "empty userId")] #[test_case(|x| x.member.as_mut().unwrap().role = proto::group::member::Role::ADMINISTRATOR.into() => Ok(()); "administrator")] #[test_case(|x| x.member.as_mut().unwrap().role = proto::group::member::Role::UNKNOWN.into() => Err(GroupError::MemberRoleUnknown); "role unknown")] - #[test_case(|x| x.member.as_mut().unwrap().profileKey = proto::Contact::TEST_PROFILE_KEY.to_vec() => Err(GroupError::MemberPendingProfileKeyHasProfileKey); "valid profileKey")] #[test_case(|x| x.addedByUserId = Pni::from_uuid_bytes(proto::Contact::TEST_PNI).service_id_binary() => Err(GroupError::MemberInvalidAci { which: "inviter", found: ServiceIdKind::Pni }); "PNI inviter")] #[test_case(|x| x.addedByUserId = vec![] => Err(GroupError::MemberInvalidServiceId { which: "inviter" }); "empty inviter")] #[test_case(|x| x.addedByUserId = proto::Contact::TEST_ACI.to_vec() => Err(GroupError::MemberPendingProfileKeyWasInvitedBySelf); "self-invite")] @@ -364,7 +339,6 @@ mod tests { pub(crate) fn test_data() -> Self { Self { userId: proto::Contact::TEST_ACI.to_vec(), - profileKey: proto::Contact::TEST_PROFILE_KEY.to_vec(), timestamp: MillisecondsSinceEpoch::TEST_VALUE.0, ..Default::default() } @@ -375,7 +349,6 @@ mod tests { pub(crate) fn from_proto_test_data() -> Self { Self { user_id: Aci::from_uuid_bytes(proto::Contact::TEST_ACI), - profile_key: proto::Contact::TEST_PROFILE_KEY, timestamp: Timestamp::test_value(), _limit_construction_to_module: (), } @@ -395,7 +368,6 @@ mod tests { #[test_case(|x| x.userId = Pni::from_uuid_bytes(proto::Contact::TEST_PNI).service_id_binary() => Err(GroupError::MemberInvalidAci { which: "requesting member", found: ServiceIdKind::Pni }); "PNI userId")] #[test_case(|x| x.userId = vec![] => Err(GroupError::MemberInvalidServiceId { which: "requesting member" }); "empty userId")] - #[test_case(|x| x.profileKey = vec![] => Err(GroupError::MemberInvalidProfileKey); "empty profileKey")] fn member_pending_admin_approval( modifier: impl FnOnce(&mut proto::group::MemberPendingAdminApproval), ) -> Result<(), GroupError> { diff --git a/rust/message-backup/src/proto/backup.proto b/rust/message-backup/src/proto/backup.proto index b2bc3c7c..4edee91c 100644 --- a/rust/message-backup/src/proto/backup.proto +++ b/rust/message-backup/src/proto/backup.proto @@ -191,8 +191,8 @@ message Group { bytes userId = 1; Role role = 2; - bytes profileKey = 3; - reserved /*presentation*/ 4; // The field is deprecated in the context of static group state + reserved /*profileKey*/ 3; // This field is ignored in Backups, in favor of Contact frames for members + reserved /*presentation*/ 4; // This field is deprecated in the context of static group state uint32 joinedAtVersion = 5; } @@ -204,8 +204,8 @@ message Group { message MemberPendingAdminApproval { bytes userId = 1; - bytes profileKey = 2; - reserved /*presentation*/ 3; // The field is deprecated in the context of static group state + reserved /*profileKey*/ 2; // This field is ignored in Backups, in favor of Contact frames for members + reserved /*presentation*/ 3; // This field is deprecated in the context of static group state uint64 timestamp = 4; } @@ -243,6 +243,7 @@ message Chat { bool markedUnread = 7; bool dontNotifyForMentionsIfMuted = 8; ChatStyle style = 9; + uint32 expireTimerVersion = 10; } /** @@ -1066,7 +1067,7 @@ message StickerPack { message ChatStyle { message Gradient { uint32 angle = 1; // degrees - repeated fixed32 colors = 2; + repeated fixed32 colors = 2; // 0xAARRGGBB repeated float positions = 3; // percent from 0 to 1 } @@ -1074,7 +1075,7 @@ message ChatStyle { uint64 id = 1; oneof color { - fixed32 solid = 2; + fixed32 solid = 2; // 0xAARRGGBB Gradient gradient = 3; } } @@ -1135,6 +1136,8 @@ message ChatStyle { oneof wallpaper { WallpaperPreset wallpaperPreset = 1; + // This `FilePointer` is expected not to contain a `fileName`, `width`, + // `height`, or `caption`. FilePointer wallpaperPhoto = 2; } diff --git a/rust/message-backup/tests/res/test-cases/valid/incoming-message-with-edits.jsonproto b/rust/message-backup/tests/res/test-cases/valid/incoming-message-with-edits.jsonproto index 05fb6bf4..1916e9ca 100644 --- a/rust/message-backup/tests/res/test-cases/valid/incoming-message-with-edits.jsonproto +++ b/rust/message-backup/tests/res/test-cases/valid/incoming-message-with-edits.jsonproto @@ -113,13 +113,11 @@ { "userId": "CujGyCcHTqeyJHQXIn04kA==", // Chewie's ACI "role": "ADMINISTRATOR", - "profileKey": "cM4PAiE6xclFBl2wesio4S/tpbDfZHFpYf7BBAsnZI4=", "joinedAtVersion": 0, }, { "userId": "X4xWjQEZR72BqruHybcZlQ==", // Han's ACI "role": "ADMINISTRATOR", - "profileKey": "YtHHVK+Wo4nPcVpWhC3roMEDu2Tw6kYc9JpLRMq1Q94=", "joinedAtVersion": 0, }, ],