0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-19 19:42:19 +02:00

backup: Make message backup types serializable

This commit is contained in:
Alex Konradi 2024-07-12 15:20:25 -04:00 committed by GitHub
parent 07801c8153
commit 784164d4bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 385 additions and 88 deletions

2
Cargo.lock generated
View File

@ -2171,6 +2171,7 @@ dependencies = [
"protobuf",
"protobuf-codegen",
"protobuf-json-mapping",
"serde",
"serde_json",
"sha2",
"signal-crypto",
@ -4518,6 +4519,7 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"serde",
"sha1_smol",
]

View File

@ -40,7 +40,7 @@ derive-where = "1.2.5"
displaydoc = "0.2.5"
env_logger = "0.10.0"
futures = "0.3.29"
hex = "0.4.3"
hex = { version = "0.4.3", features = ["serde"] }
hkdf = "0.12"
hmac = "0.12"
itertools = "0.12.0"
@ -50,6 +50,7 @@ mediasan-common = "0.5.0"
num_enum = "0.6.1"
protobuf = "3.3.0"
protobuf-json-mapping = { version = "3.3.0", optional = true }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = { version = "1.0", optional = true }
sha2 = "0.10"
smallvec = "1.13.2"
@ -60,7 +61,7 @@ strum = { version = "0.26", features = ["derive"] }
strum_macros = { version = "0.26.4" }
subtle = "2.5.0"
thiserror = "1.0.50"
uuid = "1.1.2"
uuid = { version = "1.1.2", features = ["serde"] }
[dev-dependencies]
libsignal-message-backup = { path = "./", features = ["json"] }

View File

@ -31,6 +31,7 @@ mod file;
mod frame;
pub(crate) mod method;
mod recipient;
mod serialize;
mod sticker;
mod time;
@ -38,12 +39,12 @@ pub trait ReferencedTypes {
/// Recorded information from a [`proto::Recipient`].
type RecipientData: Debug + AsRef<DestinationKind>;
/// Resolved data for a [`RecipientId`] in a non-`proto::Recipient` message.
type RecipientReference: Clone + Debug;
type RecipientReference: Clone + Debug + serde::Serialize;
/// Recorded information from a [`proto::chat_style::CustomChatColor`].
type CustomColorData: Debug + From<CustomChatColor>;
type CustomColorData: Debug + From<CustomChatColor> + serde::Serialize;
/// Resolved data for a [`CustomColorId`] in a non-`CustomChatColor` message.
type CustomColorReference: Clone + Debug;
type CustomColorReference: Clone + Debug + serde::Serialize;
fn color_reference<'a>(
id: &'a CustomColorId,
@ -98,7 +99,7 @@ struct ChatsData<M: Method + ReferencedTypes> {
pub chat_items_count: usize,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
pub struct Backup {
pub meta: BackupMeta,
pub account_data: AccountData<Store>,
@ -108,7 +109,7 @@ pub struct Backup {
pub sticker_packs: HashMap<StickerPackId, StickerPack<Store>>,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
pub struct BackupMeta {
/// The version of the backup format being parsed.
pub version: u64,
@ -128,6 +129,7 @@ pub struct BackupMeta {
strum::EnumString,
strum::Display,
strum::IntoStaticStr,
serde::Serialize,
)]
pub enum Purpose {
/// Intended for immediate transfer from one device to another.

View File

@ -15,10 +15,11 @@ use zkgroup::ProfileKeyBytes;
use crate::backup::chat::chat_style::{ChatStyle, ChatStyleError, CustomColorMap};
use crate::backup::method::Method;
use crate::backup::time::Duration;
use crate::backup::{ReferencedTypes, TryIntoWith as _};
use crate::backup::{serialize, ReferencedTypes, TryIntoWith as _};
use crate::proto::backup as proto;
#[derive_where(Debug)]
#[derive(serde::Serialize)]
#[cfg_attr(test, derive_where(PartialEq;
M::Value<ProfileKeyBytes>: PartialEq,
M::Value<Option<UsernameData>>: PartialEq,
@ -27,6 +28,10 @@ use crate::proto::backup as proto;
AccountSettings<M>: PartialEq,
))]
pub struct AccountData<M: Method + ReferencedTypes> {
#[serde(
with = "hex",
bound(serialize = "M::Value<ProfileKeyBytes>: AsRef<[u8]>")
)]
pub profile_key: M::Value<ProfileKeyBytes>,
pub username: M::Value<Option<UsernameData>>,
pub given_name: M::Value<String>,
@ -37,24 +42,29 @@ pub struct AccountData<M: Method + ReferencedTypes> {
pub backup_subscription: M::Value<Option<Subscription>>,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct UsernameData {
#[serde(serialize_with = "serialize::to_string")]
pub username: Username,
pub link: Option<UsernameLink>,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct UsernameLink {
#[serde(serialize_with = "serialize::enum_as_string")]
pub color: crate::proto::backup::account_data::username_link::Color,
#[serde(serialize_with = "hex::serialize")]
pub entropy: [u8; USERNAME_LINK_ENTROPY_SIZE],
#[serde(serialize_with = "serialize::to_string")]
pub server_id: Uuid,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Subscription {
#[serde(with = "hex")]
pub subscriber_id: SubscriberId,
pub currency_code: String,
pub manually_canceled: bool,
@ -64,6 +74,7 @@ const SUBSCRIBER_ID_LENGTH: usize = 32;
type SubscriberId = [u8; SUBSCRIBER_ID_LENGTH];
#[derive_where(Debug)]
#[derive(serde::Serialize)]
#[cfg_attr(test, derive_where(PartialEq;
M::Value<PhoneSharing>: PartialEq,
M::Value<Option<bool>>: PartialEq,
@ -95,7 +106,7 @@ pub struct AccountSettings<M: Method + ReferencedTypes> {
pub custom_chat_colors: CustomColorMap<M>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize)]
pub enum PhoneSharing {
WithEverybody,
WithNobody,

View File

@ -11,7 +11,7 @@ use crate::backup::TryFromWith;
use crate::proto::backup as proto;
/// Validated version of [`proto::AdHocCall`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct AdHocCall<Recipient> {
pub id: CallId,
@ -20,7 +20,7 @@ pub struct AdHocCall<Recipient> {
}
/// Validated version of [`proto::IndividualCall`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct IndividualCall {
pub id: Option<CallId>,
@ -31,7 +31,7 @@ pub struct IndividualCall {
}
/// Validated version of [`proto::GroupCall`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct GroupCall<Recipient> {
pub id: Option<CallId>,
@ -45,7 +45,7 @@ pub struct GroupCall<Recipient> {
///
/// This is not referenced as a foreign key from elsewhere in a backup, but
/// corresponds to shared state across conversation members for a given call.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct CallId(u64);
#[derive(Debug, displaydoc::Display, thiserror::Error)]
@ -78,14 +78,14 @@ pub enum CallLinkError {
InvalidRootKey(usize),
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum CallType {
Audio,
Video,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum IndividualCallState {
Accepted,
@ -94,7 +94,7 @@ pub enum IndividualCallState {
Missed,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum GroupCallState {
/// No ring
@ -122,7 +122,7 @@ const CALL_LINK_ROOT_KEY_LEN: usize = 16;
type CallLinkRootKey = [u8; CALL_LINK_ROOT_KEY_LEN];
/// Validated version of [`proto::CallLink`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct CallLink {
pub admin_approval: bool,

View File

@ -139,6 +139,7 @@ pub struct InvalidExpiration {
/// Validated version of [`proto::Chat`].
#[derive_where(Debug)]
#[derive(serde::Serialize)]
#[cfg_attr(test, derive_where(PartialEq;
M::List<ChatItemData<M>>: PartialEq,
M::RecipientReference: PartialEq,
@ -146,6 +147,7 @@ pub struct InvalidExpiration {
))]
pub struct ChatData<M: Method + ReferencedTypes> {
pub recipient: M::RecipientReference,
#[serde(bound(serialize = "M::List<ChatItemData<M>>: serde::Serialize"))]
pub items: M::List<ChatItemData<M>>,
pub expiration_timer: Option<Duration>,
pub mute_until: Option<Timestamp>,
@ -156,18 +158,21 @@ pub struct ChatData<M: Method + ReferencedTypes> {
pub archived: bool,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, serde::Serialize)]
pub struct PinOrder(NonZeroU32);
/// Validated version of [`proto::ChatItem`].
#[derive_where(Debug)]
#[derive(serde::Serialize)]
#[cfg_attr(test, derive_where(PartialEq;
ChatItemMessage<M>: PartialEq,
M::RecipientReference: PartialEq
))]
pub struct ChatItemData<M: Method + ReferencedTypes> {
pub author: M::RecipientReference,
#[serde(bound(serialize = "ChatItemMessage<M>: serde::Serialize"))]
pub message: ChatItemMessage<M>,
// This could be Self: Serialize but that just confuses the compiler.
pub revisions: Vec<ChatItemData<M>>,
pub direction: Direction,
pub expire_start: Option<Timestamp>,
@ -182,6 +187,7 @@ pub struct ChatItemData<M: Method + ReferencedTypes> {
/// Validated version of [`proto::chat_item::Item`].
#[derive_where(Debug)]
#[derive(serde::Serialize)]
#[cfg_attr(test, derive_where(PartialEq;
M::BoxedValue<GiftBadge>: PartialEq,
M::RecipientReference: PartialEq
@ -194,11 +200,12 @@ pub enum ChatItemMessage<M: Method + ReferencedTypes> {
RemoteDeleted,
Update(UpdateMessage<M::RecipientReference>),
PaymentNotification(PaymentNotification),
#[serde(bound(serialize = "M::BoxedValue<GiftBadge>: serde::Serialize"))]
GiftBadge(M::BoxedValue<GiftBadge>),
}
/// Validated version of [`proto::Reaction`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Reaction {
pub emoji: String,
@ -218,7 +225,7 @@ pub enum ReactionError {
EmptyEmoji,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum Direction {
Incoming {
@ -231,7 +238,7 @@ pub enum Direction {
Directionless,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct OutgoingSend {
pub recipient: RecipientId,
@ -242,7 +249,7 @@ pub struct OutgoingSend {
pub sealed_sender: bool,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum DeliveryStatus {
Failed,

View File

@ -8,9 +8,10 @@ use derive_where::derive_where;
use itertools::Itertools as _;
use crate::backup::method::{Contains, Lookup};
use crate::backup::{ReferencedTypes, TryFromWith, TryIntoWith as _};
use crate::backup::{serialize, ReferencedTypes, TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
#[derive(serde::Serialize)]
#[derive_where(Debug)]
#[cfg_attr(test, derive_where(PartialEq; M::CustomColorReference: PartialEq))]
pub struct ChatStyle<M: ReferencedTypes> {
@ -18,26 +19,27 @@ pub struct ChatStyle<M: ReferencedTypes> {
pub bubble_color: BubbleColor<M::CustomColorReference>,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum Wallpaper {
Preset(WallpaperPreset),
Photo,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Color(#[allow(unused)] u32);
#[serde(transparent)]
pub struct Color(u32);
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct WallpaperPreset {
/// Guaranteed to not be [`proto::chat_style::WallpaperPreset::UNKNOWN_WALLPAPER_PRESET`].
#[allow(unused)]
#[serde(serialize_with = "serialize::enum_as_string")]
enum_value: proto::chat_style::WallpaperPreset,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum BubbleColor<CustomColor> {
Preset(BubbleColorPreset),
@ -45,7 +47,7 @@ pub enum BubbleColor<CustomColor> {
Auto,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct BubbleGradientColor {
pub color: Color,
@ -54,18 +56,19 @@ pub struct BubbleGradientColor {
position: f32,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct BubbleColorPreset {
/// Guaranteed to not be [`proto::chat_style::BubbleColorPreset::UNKNOWN_BUBBLE_COLOR_PRESET`].
#[allow(unused)]
#[serde(serialize_with = "serialize::enum_as_string")]
enum_value: proto::chat_style::BubbleColorPreset,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, serde::Serialize)]
pub struct CustomColorId(pub(crate) u32);
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum CustomChatColor {
Gradient {
@ -81,6 +84,7 @@ pub enum CustomChatColor {
///
/// Uses a `Vec` internally since the list is expected to be small.
#[derive_where(Debug, Default)]
#[derive(serde::Serialize)]
#[cfg_attr(test, derive_where(PartialEq; M::CustomColorData: PartialEq))]
pub struct CustomColorMap<M: ReferencedTypes>(Vec<(CustomColorId, M::CustomColorData)>);

View File

@ -11,7 +11,7 @@ use crate::backup::{TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
/// Validated version of [`proto::ContactMessage`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ContactMessage {
pub contacts: Vec<ContactAttachment>,
@ -20,7 +20,7 @@ pub struct ContactMessage {
}
/// Validated version of [`proto::ContactAttachment`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ContactAttachment {
pub name: Option<proto::contact_attachment::Name>,
@ -28,6 +28,7 @@ pub struct ContactAttachment {
pub email: Vec<proto::contact_attachment::Email>,
pub address: Vec<proto::contact_attachment::PostalAddress>,
pub organization: Option<String>,
#[serde(skip)]
_limit_construction_to_module: (),
}

View File

@ -6,10 +6,13 @@
use zkgroup::receipts::ReceiptCredentialPresentation;
use zkgroup::ZkGroupDeserializationFailure;
use crate::backup::serialize;
use crate::proto::backup as proto;
#[derive(serde::Serialize)]
pub struct GiftBadge {
receipt_credential_presentation: ReceiptCredentialPresentation,
#[serde(serialize_with = "serialize::enum_as_string")]
state: proto::gift_badge::State,
}

View File

@ -15,7 +15,7 @@ use macro_rules_attribute::macro_rules_derive;
use protobuf::{EnumOrUnknown, Message};
use crate::backup::time::Duration;
use crate::backup::uuid_bytes_to_aci;
use crate::backup::{serialize, uuid_bytes_to_aci};
use crate::proto::backup::{
self as proto, group_invitation_revoked_update, GenericGroupUpdate, GroupAdminStatusUpdate,
GroupAnnouncementOnlyChangeUpdate, GroupAttributesAccessLevelChangeUpdate, GroupAvatarUpdate,
@ -45,7 +45,10 @@ macro_rules! TryFromProto {
($( #[$attrs:meta] )*
pub enum GroupChatUpdate { $(
$VariantName:ident $({
$($field:ident : $typ:ty,)*
$(
$(#[$_attr: meta])*
$field:ident : $typ:ty,
)*
})?,
)* }) => {
// Expand using the next match for each enum variant.
@ -82,117 +85,152 @@ macro_rules! TryFromProto {
/// Validated version of [`proto::group_change_chat_update::update::Update`].
#[allow(clippy::enum_variant_names, non_snake_case)] // names taken from proto message.
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[macro_rules_derive(TryFromProto)]
#[cfg_attr(test, derive(PartialEq))]
pub enum GroupChatUpdate {
GenericGroupUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
},
GroupCreationUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
},
GroupNameUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
newGroupName: NoValidation<Option<String>>,
},
GroupAvatarUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
wasRemoved: NoValidation<bool>,
},
GroupDescriptionUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
newDescription: NoValidation<Option<String>>,
},
GroupMembershipAccessLevelChangeUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
accessLevel: AccessLevel,
},
GroupAttributesAccessLevelChangeUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
accessLevel: AccessLevel,
},
GroupAnnouncementOnlyChangeUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
isAnnouncementOnly: NoValidation<bool>,
},
GroupAdminStatusUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
#[serde(serialize_with = "serialize::service_id_as_string")]
memberAci: Aci,
wasAdminStatusGranted: NoValidation<bool>,
},
GroupMemberLeftUpdate {
#[serde(serialize_with = "serialize::service_id_as_string")]
aci: Aci,
},
GroupMemberRemovedUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
removerAci: Option<Aci>,
#[serde(serialize_with = "serialize::service_id_as_string")]
removedAci: Aci,
},
SelfInvitedToGroupUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
inviterAci: Option<Aci>,
},
SelfInvitedOtherUserToGroupUpdate {
#[serde(serialize_with = "serialize::service_id_as_string")]
inviteeServiceId: ServiceId,
},
GroupUnknownInviteeUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
inviterAci: Option<Aci>,
inviteeCount: NoValidation<u32>,
},
GroupInvitationAcceptedUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
inviterAci: Option<Aci>,
#[serde(serialize_with = "serialize::service_id_as_string")]
newMemberAci: Aci,
},
GroupInvitationDeclinedUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
inviterAci: Option<Aci>,
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
inviteeAci: Option<Aci>,
},
GroupMemberJoinedUpdate {
#[serde(serialize_with = "serialize::service_id_as_string")]
newMemberAci: Aci,
},
GroupMemberAddedUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
#[serde(serialize_with = "serialize::service_id_as_string")]
newMemberAci: Aci,
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
inviterAci: Option<Aci>,
// TODO check that this field doesn't affect the validity of other fields in
// the message.
hadOpenInvitation: NoValidation<bool>,
},
GroupSelfInvitationRevokedUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
revokerAci: Option<Aci>,
},
GroupInvitationRevokedUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
invitees: Vec<Invitee>,
},
GroupJoinRequestUpdate {
#[serde(serialize_with = "serialize::service_id_as_string")]
requestorAci: Aci,
},
GroupJoinRequestApprovalUpdate {
#[serde(serialize_with = "serialize::service_id_as_string")]
requestorAci: Aci,
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
wasApproved: NoValidation<bool>,
},
GroupJoinRequestCanceledUpdate {
#[serde(serialize_with = "serialize::service_id_as_string")]
requestorAci: Aci,
},
GroupInviteLinkResetUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
},
GroupInviteLinkEnabledUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
linkRequiresAdminApproval: NoValidation<bool>,
},
GroupInviteLinkAdminApprovalUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
linkRequiresAdminApproval: NoValidation<bool>,
},
GroupInviteLinkDisabledUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
},
GroupMemberJoinedByLinkUpdate {
#[serde(serialize_with = "serialize::service_id_as_string")]
newMemberAci: Aci,
},
GroupV2MigrationUpdate,
@ -204,16 +242,18 @@ pub enum GroupChatUpdate {
droppedMembersCount: NoValidation<u32>,
},
GroupSequenceOfRequestsAndCancelsUpdate {
#[serde(serialize_with = "serialize::service_id_as_string")]
requestorAci: Aci,
count: NoValidation<u32>,
},
GroupExpirationTimerUpdate {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
updaterAci: Option<Aci>,
expiresInMs: Duration,
},
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum AccessLevel {
Any,
@ -221,11 +261,14 @@ pub enum AccessLevel {
Administrator,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Invitee {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
pub inviter: Option<Aci>,
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
pub invitee_aci: Option<Aci>,
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
pub invitee_pni: Option<Pni>,
}
@ -238,8 +281,9 @@ pub struct GroupUpdateError {
pub field_error: GroupUpdateFieldError,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
#[serde(transparent)]
pub struct NoValidation<T>(T);
#[derive(Debug, thiserror::Error, displaydoc::Display)]

View File

@ -7,7 +7,7 @@ use crate::backup::time::Timestamp;
use crate::proto::backup as proto;
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct LinkPreview {
pub url: String,

View File

@ -5,10 +5,11 @@
use std::fmt::Display;
use crate::backup::serialize;
use crate::backup::time::Timestamp;
use crate::proto::backup as proto;
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct PaymentNotification {
pub amount: Option<MobAmount>,
@ -17,16 +18,17 @@ pub struct PaymentNotification {
pub details: Option<TransactionDetails>,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum TransactionDetails {
Transaction(Transaction),
FailedTransaction(FailedTransaction),
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Transaction {
#[serde(serialize_with = "serialize::enum_as_string")]
pub status: proto::payment_notification::transaction_details::transaction::Status,
pub identification: Option<Identification>,
pub timestamp: Option<Timestamp>,
@ -37,20 +39,23 @@ pub struct Transaction {
}
/// Wrapper around an arbitrary-precision decimal number
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
#[serde(transparent)]
pub struct MobAmount(String);
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum Identification {
Sent { key_images: Vec<Vec<u8>> },
Received { public_keys: Vec<Vec<u8>> },
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
#[serde(transparent)]
pub struct FailedTransaction {
#[serde(serialize_with = "serialize::enum_as_string")]
pub reason: proto::payment_notification::transaction_details::failed_transaction::FailureReason,
}

View File

@ -11,17 +11,18 @@ use crate::backup::TryFromWith;
use crate::proto::backup as proto;
/// Validated version of [`proto::Quote`]
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Quote {
pub author: RecipientId,
pub quote_type: QuoteType,
pub target_sent_timestamp: Option<Timestamp>,
pub text: Option<MessageText>,
#[serde(skip)]
_limit_construction_to_module: (),
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum QuoteType {
Normal,

View File

@ -12,7 +12,7 @@ use crate::backup::{TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
/// Validated version of [`proto::StandardMessage`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct StandardMessage {
pub text: Option<MessageText>,

View File

@ -10,7 +10,7 @@ use crate::backup::{TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
/// Validated version of [`proto::StickerMessage`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct StickerMessage {
pub reactions: Vec<Reaction>,

View File

@ -5,18 +5,18 @@
use libsignal_protocol::Aci;
use crate::backup::uuid_bytes_to_aci;
use crate::backup::{serialize, uuid_bytes_to_aci};
use crate::proto::backup as proto;
/// Validated version of [`proto::Text`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct MessageText {
pub text: String,
pub ranges: Vec<TextRange>,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct TextRange {
pub start: Option<u32>,
@ -24,11 +24,11 @@ pub struct TextRange {
pub effect: TextEffect,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum TextEffect {
MentionAci(Aci),
Style(proto::body_range::Style),
MentionAci(#[serde(serialize_with = "serialize::service_id_as_string")] Aci),
Style(#[serde(serialize_with = "serialize::enum_as_string")] proto::body_range::Style),
}
#[derive(Debug, displaydoc::Display, thiserror::Error)]

View File

@ -12,7 +12,7 @@ use crate::backup::{TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
/// Validated version of [`proto::chat_update_message::Update`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum UpdateMessage<Recipient> {
Simple(SimpleChatUpdate),
@ -27,7 +27,7 @@ pub enum UpdateMessage<Recipient> {
}
/// Validated version of [`proto::simple_chat_update::Type`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum SimpleChatUpdate {
JoinedSignal,

View File

@ -11,7 +11,7 @@ use crate::backup::{TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
/// Validated version of a voice message [`proto::StandardMessage`].
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct VoiceMessage {
pub quote: Option<Quote>,

View File

@ -12,10 +12,11 @@ use uuid::Uuid;
use crate::proto::backup::{self as proto, FilePointer};
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(Default, PartialEq))]
pub struct VoiceMessageAttachment {
pub client_uuid: Option<Uuid>,
#[serde(skip)]
_limit_construction_to_module: (),
}

View File

@ -6,14 +6,14 @@
use crate::backup::WithId;
use crate::proto::backup::{Chat, Recipient};
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, serde::Serialize)]
pub struct RecipientId(pub(super) u64);
/// Foreign key
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, serde::Serialize)]
pub struct ChatId(pub(super) u64);
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, serde::Serialize)]
pub struct RingerRecipientId(pub(super) RecipientId);
impl From<RingerRecipientId> for RecipientId {

View File

@ -86,16 +86,23 @@ where
}
}
pub trait Method {
type Value<T: Debug>: Debug;
// The `serde::Serialize` supertrait isn't needed for anything but it simplifies
// using the `serde::Serialize` derive macro on types that are parameterized
// over `M: Method`. The macro's heuristic assumes that all type parameters must
// implement `serde::Serialize`. That's not correct in this case but it's
// simpler to just roll with it since the no-op implementations can be trivially
// derived.
pub trait Method: serde::Serialize {
type Value<T: Debug + serde::Serialize>: Debug + serde::Serialize;
type BoxedValue<T: Debug>: Debug;
type Map<K: Eq + Hash + Debug, V: Debug>: Map<K, V> + Debug;
type List<T: Debug>: Extend<T> + Default + Debug;
fn value<T: Debug>(value: T) -> Self::Value<T>;
fn value<T: Debug + serde::Serialize>(value: T) -> Self::Value<T>;
fn boxed_value<T: Debug>(value: T) -> Self::BoxedValue<T>;
}
#[derive(serde::Serialize)]
pub enum ValidateOnly {}
#[derive(Default, Debug)]
@ -108,24 +115,25 @@ impl<T> Extend<T> for ValidateOnlyList {
}
impl Method for ValidateOnly {
type Value<T: Debug> = ();
type Value<T: Debug + serde::Serialize> = ();
type BoxedValue<T: Debug> = ();
type Map<K: Eq + Hash + Debug, V: Debug> = HashSet<K>;
type List<T: Debug> = ValidateOnlyList;
fn value<T: Debug>(_value: T) -> Self::Value<T> {}
fn value<T: Debug + serde::Serialize>(_value: T) -> Self::Value<T> {}
fn boxed_value<T: Debug>(_value: T) -> Self::BoxedValue<T> {}
}
#[derive(serde::Serialize)]
pub enum Store {}
impl Method for Store {
type Value<T: Debug> = T;
type Value<T: Debug + serde::Serialize> = T;
type BoxedValue<T: Debug> = Box<T>;
type Map<K: Eq + Hash + Debug, V: Debug> = HashMap<K, V>;
type List<T: Debug> = Vec<T>;
fn value<T: Debug>(value: T) -> Self::Value<T> {
fn value<T: Debug + serde::Serialize>(value: T) -> Self::Value<T> {
value
}
fn boxed_value<T: Debug>(value: T) -> Self::BoxedValue<T> {

View File

@ -17,7 +17,7 @@ use crate::backup::call::{CallLink, CallLinkError};
use crate::backup::frame::RecipientId;
use crate::backup::method::{LookupPair, Method, Store, ValidateOnly};
use crate::backup::time::Timestamp;
use crate::backup::{ReferencedTypes, TryFromWith, TryIntoWith};
use crate::backup::{serialize, ReferencedTypes, TryFromWith, TryIntoWith};
use crate::proto::backup as proto;
use crate::proto::backup::recipient::Destination as RecipientDestination;
@ -66,7 +66,7 @@ pub struct MinimalRecipientData(DestinationKind);
///
/// This keeps the full data in memory behind a [`Arc`] so it can be cheaply
/// cloned when referenced by later frames.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct FullRecipientData(Arc<Destination<Store>>);
#[derive_where(Debug)]
@ -78,7 +78,7 @@ pub struct FullRecipientData(Arc<Destination<Store>>);
M::Value<CallLink>: PartialEq
)
)]
#[derive(strum::EnumDiscriminants)]
#[derive(serde::Serialize, strum::EnumDiscriminants)]
#[strum_discriminants(name(DestinationKind))]
pub enum Destination<M: Method + ReferencedTypes> {
Contact(M::Value<ContactData>),
@ -95,16 +95,19 @@ impl AsRef<DestinationKind> for DestinationKind {
}
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ContactData {
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
pub aci: Option<Aci>,
#[serde(serialize_with = "serialize::optional_service_id_as_string")]
pub pni: Option<Pni>,
pub profile_key: Option<ProfileKeyBytes>,
pub username: Option<String>,
pub registration: Registration,
pub e164: Option<u64>,
pub blocked: bool,
#[serde(serialize_with = "serialize::enum_as_string")]
pub visibility: proto::contact::Visibility,
pub profile_sharing: bool,
pub profile_given_name: Option<String>,
@ -112,17 +115,19 @@ pub struct ContactData {
pub hide_story: bool,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct GroupData {
pub master_key: GroupMasterKeyBytes,
pub whitelisted: bool,
pub hide_story: bool,
#[serde(serialize_with = "serialize::enum_as_string")]
pub story_send_mode: proto::group::StorySendMode,
#[serde(serialize_with = "serialize::optional_proto_message_as_bytes")]
pub snapshot: Option<Box<proto::group::GroupSnapshot>>,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum DistributionListItem<Recipient> {
Deleted {
@ -138,14 +143,14 @@ pub enum DistributionListItem<Recipient> {
},
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum Registration {
NotRegistered { unregistered_at: Option<Timestamp> },
Registered,
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub enum PrivacyMode {
OnlyWith,

View File

@ -0,0 +1,182 @@
//
// Copyright (C) 2024 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
use libsignal_protocol::ServiceId;
use serde::ser::{SerializeStruct as _, SerializeTupleVariant as _};
use serde::{Serialize, Serializer};
use crate::proto::backup as proto;
/// Serializes using [`ToString`].
pub(crate) fn to_string<S: Serializer>(t: &impl ToString, s: S) -> Result<S::Ok, S::Error> {
t.to_string().serialize(s)
}
/// Serializes using [`ServiceId::service_id_string`].
pub(crate) fn service_id_as_string<S: Serializer>(
id: &(impl Copy + Into<ServiceId>),
serializer: S,
) -> Result<S::Ok, S::Error> {
(*id).into().service_id_string().serialize(serializer)
}
/// Serializes using [`ServiceId::service_id_string`].
pub(crate) fn optional_service_id_as_string<S: Serializer>(
id: &Option<(impl Copy + Into<ServiceId>)>,
serializer: S,
) -> Result<S::Ok, S::Error> {
(*id)
.map(|id| id.into().service_id_string())
.serialize(serializer)
}
/// Serializes [`protobuf::Enum`] types as strings.
pub(crate) fn enum_as_string<S: Serializer>(
source: &impl protobuf::Enum,
serializer: S,
) -> Result<S::Ok, S::Error> {
format!("{source:?}").serialize(serializer)
}
/// Serializes [`protobuf::Message`] types as hex-encoded protobuf wire format.
pub(crate) fn optional_proto_message_as_bytes<S: Serializer, M: protobuf::Message>(
message: &Option<impl std::ops::Deref<Target = M>>,
serializer: S,
) -> Result<S::Ok, S::Error> {
struct MessageAsHexBytes<T>(T);
impl<T: protobuf::Message> Serialize for MessageAsHexBytes<&'_ T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut bytes = Vec::new();
self.0
.write_to_vec(&mut bytes)
.map_err(<S::Error as serde::ser::Error>::custom)?;
hex::serialize(bytes, serializer)
}
}
message
.as_deref()
.map(MessageAsHexBytes)
.serialize(serializer)
}
impl serde::Serialize for proto::contact_attachment::Name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let Self {
givenName,
familyName,
prefix,
suffix,
middleName,
displayName,
special_fields: _,
} = self;
let mut ser = serializer.serialize_struct("Name", 6)?;
ser.serialize_field("givenName", givenName)?;
ser.serialize_field("familyName", familyName)?;
ser.serialize_field("prefix", prefix)?;
ser.serialize_field("suffix", suffix)?;
ser.serialize_field("middleName", middleName)?;
ser.serialize_field("displayName", displayName)?;
ser.end()
}
}
impl serde::Serialize for proto::contact_attachment::Phone {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let Self {
value,
type_,
label,
special_fields: _,
} = self;
let mut ser = serializer.serialize_struct("Phone", 3)?;
ser.serialize_field("value", value)?;
ser.serialize_field("type_", &format!("{:?}", type_))?;
ser.serialize_field("label", label)?;
ser.end()
}
}
impl serde::Serialize for proto::contact_attachment::Email {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let Self {
value,
type_,
label,
special_fields: _,
} = self;
let mut ser = serializer.serialize_struct("Email", 3)?;
ser.serialize_field("value", value)?;
ser.serialize_field("type_", &format!("{:?}", type_))?;
ser.serialize_field("label", label)?;
ser.end()
}
}
impl serde::Serialize for proto::contact_attachment::PostalAddress {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let Self {
type_,
label,
street,
pobox,
neighborhood,
city,
region,
postcode,
country,
special_fields: _,
} = self;
let mut ser = serializer.serialize_struct("PostalAddress", 9)?;
ser.serialize_field("type_", &format!("{:?}", type_))?;
ser.serialize_field("label", label)?;
ser.serialize_field("street", street)?;
ser.serialize_field("pobox", pobox)?;
ser.serialize_field("neighborhood", neighborhood)?;
ser.serialize_field("city", city)?;
ser.serialize_field("region", region)?;
ser.serialize_field("postcode", postcode)?;
ser.serialize_field("country", country)?;
ser.end()
}
}
impl serde::Serialize for proto::learned_profile_chat_update::PreviousName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
proto::learned_profile_chat_update::PreviousName::E164(e164) => {
let mut tv = serializer.serialize_tuple_variant("PreviousName", 0, "E164", 1)?;
tv.serialize_field(e164)?;
tv.end()
}
proto::learned_profile_chat_update::PreviousName::Username(username) => {
let mut tv =
serializer.serialize_tuple_variant("PreviousName", 1, "Username", 1)?;
tv.serialize_field(username)?;
tv.end()
}
}
}
}

View File

@ -15,6 +15,7 @@ use crate::proto::backup as proto;
/// Validated version of [`proto::StickerPack`].
#[derive_where(Debug)]
#[derive(serde::Serialize)]
#[cfg_attr(test, derive_where(PartialEq;
M::Value<Key>: PartialEq,
))]
@ -23,7 +24,7 @@ pub struct StickerPack<M: Method> {
_limit_construction_to_module: (),
}
#[derive(Debug)]
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct MessageSticker {
pub pack_id: PackId,
@ -32,10 +33,10 @@ pub struct MessageSticker {
_limit_construction_to_module: (),
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, serde::Serialize)]
pub struct PackId([u8; 16]);
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, serde::Serialize)]
pub struct Key([u8; 32]);
impl<'a> TryFrom<&'a [u8]> for PackId {

View File

@ -6,10 +6,10 @@
use std::ops::Add;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
pub struct Timestamp(SystemTime);
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
pub struct Duration(std::time::Duration);
impl Timestamp {

View File

@ -46,6 +46,25 @@ fn is_valid_binary_proto(input: Fixture<Vec<u8>>) {
validate_proto(input.content())
}
#[dir_test(
dir: "$CARGO_MANIFEST_DIR/tests/res/test-cases",
glob: "valid/*.binproto",
postfix: "serialize"
loader: read_file
)]
fn can_serialize_binary_proto(input: Fixture<Vec<u8>>) {
let input = Cursor::new(input.content());
let reader = BackupReader::new_unencrypted(input, BACKUP_PURPOSE);
let result = futures::executor::block_on(reader.read_all())
.result
.expect("valid backup");
// This should not crash.
println!(
"{}",
serde_json::to_string_pretty(&result).expect("can serialize")
)
}
const ENCRYPTED_SOURCE_SUFFIX: &str = ".source.jsonproto";
#[dir_test(
dir: "$CARGO_MANIFEST_DIR/tests/res/test-cases",