Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Threema/libthreema/lib/src/data_backup/   (Social Medium ©)  Datei vom 26.9.2026 mit Größe 76 kB image not shown  

Quelle  codec.rs   Sprache: Rust

 

//! Codecs for the data backup.
use crate::{data_backup::DataBackupDecodeErrorSource, utils::debug::Name};

trait InternalBackupDecoder<TRawMessage: Name>: Sized {
    fn decode(raw_message: TRawMessage) -> Result<Self, DataBackupDecodeErrorSource>;

    // TODO(LIB-174): Check: Move this to utils/protobuf.rs ?
    //
    // Idea: Extend prost::Message and expose this for all protobuf messages.
    fn expect_field<T>(value: Option<T>, field_name: &'static str) -> Result<T, DataBackupDecodeErrorSource> {
        value.ok_or(DataBackupDecodeErrorSource::MissingField {
            name: TRawMessage::NAME,
            field_name,
        })
    }

    // TODO(LIB-174): Check: Move this to utils/protobuf.rs ?
    fn parse_enum_variant<T>(variant: i32) -> Result<T, DataBackupDecodeErrorSource>
    where
        T: TryFrom<i32> + Name,
    {
        variant
            .try_into()
            .map_err(|_| DataBackupDecodeErrorSource::UnknownEnumVariant {
                name: T::NAME,
                variant,
            })
    }

    /// Decode an optional enum variant and return a [`BackupDecodingError`] if the value is `None` or if the
    /// variant could not be decoded.
    // TODO(LIB-174): Check: Move this to utils/protobuf.rs ?
    // TODO(LIB-174): Is there a method provided by prost? Otherwise, we should add an extension.
    fn expect_and_parse_enum_variant<T>(
        variant: Option<i32>,
        field_name: &'static str,
    ) -> Result<T, DataBackupDecodeErrorSource>
    where
        T: TryFrom<i32> + Name,
    {
        Self::parse_enum_variant::<T>(Self::expect_field(variant, field_name)?)
    }
}

trait InternalBackupEncoder<TOutput> {
    fn encode(self) -> TOutput;
}

mod common {
    use std::collections::HashSet;

    use crate::{
        common::{ConversationId, DistributionListId, GroupIdentity, ThreemaId},
        data_backup::{
            DataBackupDecodeErrorSource,
            codec::{InternalBackupDecoder, InternalBackupEncoder},
        },
        protobuf,
    };

    // TODO(LIB-174): Is there a better place for this? Maybe even TryFrom/From implementations?
    impl InternalBackupDecoder<protobuf::common::Identities> for Vec<ThreemaId> {
        fn decode(raw_message: protobuf::common::Identities) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::common::Identities { identities } = raw_message;

            Ok(identities
                .into_iter()
                .map(|identity| ThreemaId::try_from(identity.as_str()))
                .collect::<Result<_, _>>()?)
        }
    }
    impl InternalBackupEncoder<protobuf::common::Identities> for Vec<ThreemaId> {
        fn encode(self) -> protobuf::common::Identities {
            protobuf::common::Identities {
                identities: self.into_iter().map(ThreemaId::into).collect(),
            }
        }
    }

    impl InternalBackupDecoder<protobuf::common::Identities> for HashSet<ThreemaId> {
        fn decode(raw_message: protobuf::common::Identities) -> Result<Self, DataBackupDecodeErrorSource> {
            Ok(Vec::decode(raw_message)?.into_iter().collect())
        }
    }
    impl InternalBackupEncoder<protobuf::common::Identities> for HashSet<ThreemaId> {
        fn encode(self) -> protobuf::common::Identities {
            protobuf::common::Identities {
                identities: self.into_iter().map(ThreemaId::into).collect(),
            }
        }
    }

    impl InternalBackupDecoder<protobuf::d2d::ConversationId> for ConversationId {
        fn decode(raw_message: protobuf::d2d::ConversationId) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::d2d::ConversationId { id: conversation_id } = raw_message;

            Ok(match Self::expect_field(conversation_id, "conversation_id")? {
                protobuf::d2d::conversation_id::Id::Contact(threema_id) => {
                    Self::Contact(ThreemaId::try_from(threema_id.as_str())?)
                },
                protobuf::d2d::conversation_id::Id::DistributionList(distribution_list_id) => {
                    Self::DistributionList(DistributionListId(distribution_list_id))
                },
                protobuf::d2d::conversation_id::Id::Group(group_identity) => {
                    Self::Group(GroupIdentity::decode(group_identity)?)
                },
            })
        }
    }
    impl InternalBackupEncoder<protobuf::d2d::ConversationId> for ConversationId {
        fn encode(self) -> protobuf::d2d::ConversationId {
            protobuf::d2d::ConversationId {
                id: Some(match self {
                    Self::Contact(threema_id) => {
                        protobuf::d2d::conversation_id::Id::Contact(threema_id.to_string())
                    },
                    Self::DistributionList(distribution_list_id) => {
                        protobuf::d2d::conversation_id::Id::DistributionList(distribution_list_id.0)
                    },
                    Self::Group(group_identity) => {
                        protobuf::d2d::conversation_id::Id::Group(group_identity.encode())
                    },
                }),
            }
        }
    }
}

mod profile {
    use crate::{
        data_backup::codec::{DataBackupDecodeErrorSource, InternalBackupDecoder, InternalBackupEncoder},
        model::profile::{ProfilePicture, ProfilePictureMediaType},
        protobuf,
    };

    impl InternalBackupDecoder<protobuf::data_backup::ProfilePicture> for ProfilePicture {
        fn decode(
            raw_message: protobuf::data_backup::ProfilePicture,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::ProfilePicture { media_type, data } = raw_message;

            // Parse the format type.
            let media_type = {
                let media_type: protobuf::common::image::Type = Self::parse_enum_variant(media_type)?;
                match media_type {
                    protobuf::common::image::Type::Jpeg => ProfilePictureMediaType::Jpeg,
                }
            };

            Ok(Self { media_type, data })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::ProfilePicture> for ProfilePicture {
        fn encode(self) -> protobuf::data_backup::ProfilePicture {
            protobuf::data_backup::ProfilePicture {
                media_type: match self.media_type {
                    ProfilePictureMediaType::Jpeg => protobuf::common::image::Type::Jpeg,
                }
                .into(),
                data: self.data,
            }
        }
    }
}

mod contact {
    use crate::{
        common::{FeatureMask, ThreemaId, keys::PublicKey},
        data_backup::codec::{DataBackupDecodeErrorSource, InternalBackupDecoder, InternalBackupEncoder},
        model::{
            contact::{
                Contact, ContactNotificationTriggerPolicyOverride,
                ContactNotificationTriggerPolicyOverridePolicy, ContactWithProfilePictures,
            },
            profile::ProfilePicture,
        },
        protobuf::{
            self,
            d2d_sync::{
                self,
                contact::notification_trigger_policy_override::{
                    self, policy as notification_trigger_policy,
                },
            },
            data_backup,
        },
        work::properties::WorkAvailabilityStatus,
    };

    impl InternalBackupDecoder<notification_trigger_policy_override::Policy>
        for ContactNotificationTriggerPolicyOverride
    {
        fn decode(
            raw_message: notification_trigger_policy_override::Policy,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let notification_trigger_policy_override::Policy { policy, expires_at } = raw_message;
            let policy = {
                let policy: notification_trigger_policy_override::policy::NotificationTriggerPolicy =
                    Self::parse_enum_variant(policy)?;
                match policy {
                    notification_trigger_policy_override::policy::NotificationTriggerPolicy::Never => {
                        ContactNotificationTriggerPolicyOverridePolicy::Never
                    },
                }
            };

            Ok(Self { policy, expires_at })
        }
    }

    impl InternalBackupDecoder<d2d_sync::contact::NotificationTriggerPolicyOverride>
        for Option<ContactNotificationTriggerPolicyOverride>
    {
        fn decode(
            raw_message: d2d_sync::contact::NotificationTriggerPolicyOverride,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let d2d_sync::contact::NotificationTriggerPolicyOverride { r#override } = raw_message;

            Ok(match Self::expect_field(r#override, "override")? {
                notification_trigger_policy_override::Override::Default(_) => None,
                notification_trigger_policy_override::Override::Policy(policy) => {
                    Some(ContactNotificationTriggerPolicyOverride::decode(policy)?)
                },
            })
        }
    }
    impl InternalBackupEncoder<d2d_sync::contact::NotificationTriggerPolicyOverride>
        for Option<ContactNotificationTriggerPolicyOverride>
    {
        fn encode(self) -> d2d_sync::contact::NotificationTriggerPolicyOverride {
            d2d_sync::contact::NotificationTriggerPolicyOverride {
                r#override: Some(match self {
                    Some(policy) => notification_trigger_policy_override::Override::Policy(
                        notification_trigger_policy_override::Policy {
                            policy: match policy.policy {
                                ContactNotificationTriggerPolicyOverridePolicy::Never => {
                                    notification_trigger_policy::NotificationTriggerPolicy::Never
                                },
                            }
                            .into(),
                            expires_at: policy.expires_at,
                        },
                    ),
                    None => {
                        notification_trigger_policy_override::Override::Default(protobuf::common::Unit {})
                    },
                }),
            }
        }
    }

    impl InternalBackupDecoder<d2d_sync::WorkAvailabilityStatus> for WorkAvailabilityStatus {
        fn decode(
            raw_message: d2d_sync::WorkAvailabilityStatus,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let d2d_sync::WorkAvailabilityStatus {
                category,
                description,
            } = raw_message;

            Ok(match Self::parse_enum_variant(category)? {
                d2d_sync::WorkAvailabilityStatusCategory::None => Self::None,
                d2d_sync::WorkAvailabilityStatusCategory::Unavailable => Self::Unavailable(description),
                d2d_sync::WorkAvailabilityStatusCategory::Busy => Self::Busy(description),
            })
        }
    }
    impl InternalBackupEncoder<d2d_sync::WorkAvailabilityStatus> for WorkAvailabilityStatus {
        fn encode(self) -> d2d_sync::WorkAvailabilityStatus {
            let (category, description) = match self {
                Self::None => (d2d_sync::WorkAvailabilityStatusCategory::None, String::new()),
                Self::Unavailable(description) => {
                    (d2d_sync::WorkAvailabilityStatusCategory::Unavailable, description)
                },
                Self::Busy(description) => (d2d_sync::WorkAvailabilityStatusCategory::Busy, description),
            };

            d2d_sync::WorkAvailabilityStatus {
                category: category.into(),
                description,
            }
        }
    }

    impl InternalBackupDecoder<data_backup::AugmentedContact> for ContactWithProfilePictures {
        fn decode(raw_message: data_backup::AugmentedContact) -> Result<Self, DataBackupDecodeErrorSource> {
            let data_backup::AugmentedContact {
                contact,
                last_update_at,
                contact_defined_profile_picture,
                user_defined_profile_picture,
            } = raw_message;

            let d2d_sync::Contact {
                identity,
                public_key,
                created_at,
                first_name,
                last_name,
                nickname,
                verification_level,
                work_verification_level,
                identity_type,
                acquaintance_level,
                activity_state,
                feature_mask,
                sync_state,
                work_last_full_sync_at,
                work_availability_status,
                read_receipt_policy_override,
                typing_indicator_policy_override,
                notification_trigger_policy_override,

                #[expect(deprecated, reason = "Must be handled until phased out")]
                deprecated_notification_sound_policy_override,

                contact_defined_profile_picture: d2d_sync_contact_defined_profile_picture,
                user_defined_profile_picture: d2d_sync_user_defined_profile_picture,
                conversation_category,
                conversation_visibility,
            } = Self::expect_field(contact, "contact")?;

            if deprecated_notification_sound_policy_override.is_some() {
                return Err(DataBackupDecodeErrorSource::InvalidMessage(
                    "deprecated_notification_sound_policy_override must be None in d2d_sync::Contact"
                        .to_owned(),
                ));
            }

            if d2d_sync_contact_defined_profile_picture.is_some() {
                return Err(DataBackupDecodeErrorSource::InvalidMessage(
                    "contact_defined_profile_picture must be None in d2d_sync::Contact".to_owned(),
                ));
            }

            if d2d_sync_user_defined_profile_picture.is_some() {
                return Err(DataBackupDecodeErrorSource::InvalidMessage(
                    "user_defined_profile_picture must be None in d2d_sync::Contact".to_owned(),
                ));
            }

            let public_key = PublicKey::try_from(Self::expect_field(public_key, "public_key")?.as_slice())
                .map_err(|_| {
                    DataBackupDecodeErrorSource::InvalidMessage("Public key must have 32 bytes".to_owned())
                })?;

            let created_at = Self::expect_field(created_at, "created_at")?;

            let read_receipt_policy_override = read_receipt_policy_override
                .map(|policy_override| {
                    match Self::expect_field(policy_override.r#override, "policy_override.override")? {
                        d2d_sync::contact::read_receipt_policy_override::Override::Default(_) => {
                            Ok(d2d_sync::ReadReceiptPolicy::default())
                        },
                        d2d_sync::contact::read_receipt_policy_override::Override::Policy(policy) => {
                            Self::parse_enum_variant(policy)
                        },
                    }
                })
                .transpose()?;

            let typing_indicator_policy_override = typing_indicator_policy_override
                .map(|policy_override| {
                    match Self::expect_field(policy_override.r#override, "policy_override.override")? {
                        d2d_sync::contact::typing_indicator_policy_override::Override::Default(_) => {
                            Ok(d2d_sync::TypingIndicatorPolicy::default())
                        },
                        d2d_sync::contact::typing_indicator_policy_override::Override::Policy(policy) => {
                            Self::parse_enum_variant(policy)
                        },
                    }
                })
                .transpose()?;

            let contact = Contact {
                identity: ThreemaId::try_from(identity.as_str())?,
                public_key,
                created_at,
                last_update_at,
                first_name: Self::expect_field(first_name, "first_name")?,
                last_name: Self::expect_field(last_name, "last_name")?,
                nickname: Self::expect_field(nickname, "nickname")?,
                verification_level: Self::expect_and_parse_enum_variant(
                    verification_level,
                    "verification_level",
                )?,
                work_verification_level: Self::expect_and_parse_enum_variant(
                    work_verification_level,
                    "work_verification_level",
                )?,
                identity_type: Self::expect_and_parse_enum_variant(identity_type, "identity type")?,
                acquaintance_level: Self::expect_and_parse_enum_variant(
                    acquaintance_level,
                    "acquaintance_level",
                )?,
                activity_state: Self::expect_and_parse_enum_variant(activity_state, "activity_state")?,
                feature_mask: FeatureMask(Self::expect_field(feature_mask, "feature_mask")?),
                sync_state: Self::expect_and_parse_enum_variant(sync_state, "sync_state")?,
                work_last_full_sync_at,
                work_availability_status: work_availability_status
                    .map(WorkAvailabilityStatus::decode)
                    .transpose()?,
                read_receipt_policy_override,
                typing_indicator_policy_override,
                notification_trigger_policy_override: Option::decode(Self::expect_field(
                    notification_trigger_policy_override,
                    "notification_trigger_policy_override",
                )?)?,
                conversation_category: Self::expect_and_parse_enum_variant(
                    conversation_category,
                    "conversation_category",
                )?,
                conversation_visibility: Self::expect_and_parse_enum_variant(
                    conversation_visibility,
                    "conversation_visibility",
                )?,
            };

            Ok(Self {
                contact,
                contact_defined_profile_picture: contact_defined_profile_picture
                    .map(ProfilePicture::decode)
                    .transpose()?,
                user_defined_profile_picture: user_defined_profile_picture
                    .map(ProfilePicture::decode)
                    .transpose()?,
            })
        }
    }
    impl InternalBackupEncoder<data_backup::AugmentedContact> for ContactWithProfilePictures {
        fn encode(self) -> data_backup::AugmentedContact {
            let contact = d2d_sync::Contact {
                identity: self.contact.identity.to_string(),
                public_key: Some(self.contact.public_key.0.as_bytes().to_vec()),
                created_at: Some(self.contact.created_at),
                first_name: Some(self.contact.first_name),
                last_name: Some(self.contact.last_name),
                nickname: Some(self.contact.nickname),
                verification_level: Some(self.contact.verification_level as i32),
                work_verification_level: Some(self.contact.work_verification_level as i32),
                identity_type: Some(self.contact.identity_type as i32),
                acquaintance_level: Some(self.contact.acquaintance_level as i32),
                activity_state: Some(self.contact.activity_state as i32),
                feature_mask: Some(self.contact.feature_mask.0),
                sync_state: Some(self.contact.sync_state as i32),
                work_last_full_sync_at: self.contact.work_last_full_sync_at,
                work_availability_status: self
                    .contact
                    .work_availability_status
                    .map(WorkAvailabilityStatus::encode),
                read_receipt_policy_override: self.contact.read_receipt_policy_override.map(|policy| {
                    d2d_sync::contact::ReadReceiptPolicyOverride {
                        r#override: Some(d2d_sync::contact::read_receipt_policy_override::Override::Policy(
                            policy as i32,
                        )),
                    }
                }),
                typing_indicator_policy_override: self.contact.typing_indicator_policy_override.map(
                    |policy| d2d_sync::contact::TypingIndicatorPolicyOverride {
                        r#override: Some(
                            d2d_sync::contact::typing_indicator_policy_override::Override::Policy(
                                policy as i32,
                            ),
                        ),
                    },
                ),
                notification_trigger_policy_override: Some(
                    self.contact.notification_trigger_policy_override.encode(),
                ),
                contact_defined_profile_picture: None,
                user_defined_profile_picture: None,
                conversation_category: Some(self.contact.conversation_category as i32),
                conversation_visibility: Some(self.contact.conversation_visibility as i32),

                #[expect(deprecated, reason = "Must be handled until phased out")]
                deprecated_notification_sound_policy_override: None,
            };

            data_backup::AugmentedContact {
                contact: Some(contact),
                last_update_at: self.contact.last_update_at,
                contact_defined_profile_picture: self
                    .contact_defined_profile_picture
                    .map(ProfilePicture::encode),
                user_defined_profile_picture: self.user_defined_profile_picture.map(ProfilePicture::encode),
            }
        }
    }
}

mod distribution_list {
    use crate::{
        common::ThreemaId,
        data_backup::codec::{DataBackupDecodeErrorSource, InternalBackupDecoder, InternalBackupEncoder},
        model::distribution_list::DistributionList,
        protobuf,
    };

    impl InternalBackupDecoder<protobuf::d2d_join::essential_data::AugmentedDistributionList>
        for DistributionList
    {
        fn decode(
            raw_message: protobuf::d2d_join::essential_data::AugmentedDistributionList,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::d2d_join::essential_data::AugmentedDistributionList {
                distribution_list,
                last_update_at,
            } = raw_message;
            let protobuf::d2d_sync::DistributionList {
                distribution_list_id,
                name,
                created_at,
                member_identities,
                conversation_category,
                conversation_visibility,
            } = Self::expect_field(distribution_list, "distribution_list")?;

            Ok(Self {
                identity: crate::common::DistributionListId(distribution_list_id),
                name: Self::expect_field(name, "name")?,
                created_at: Self::expect_field(created_at, "created_at")?,
                last_update_at,
                members: Self::expect_field(member_identities, "member_identities")?
                    .identities
                    .into_iter()
                    .map(|identity| ThreemaId::try_from(identity.as_str()))
                    .collect::<Result<_, _>>()?,
                conversation_category: Self::expect_and_parse_enum_variant(
                    conversation_category,
                    "conversation_category",
                )?,
                conversation_visibility: Self::expect_and_parse_enum_variant(
                    conversation_visibility,
                    "conversation_visibility",
                )?,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::d2d_join::essential_data::AugmentedDistributionList>
        for DistributionList
    {
        fn encode(self) -> protobuf::d2d_join::essential_data::AugmentedDistributionList {
            protobuf::d2d_join::essential_data::AugmentedDistributionList {
                distribution_list: Some(protobuf::d2d_sync::DistributionList {
                    distribution_list_id: self.identity.0,
                    name: Some(self.name),
                    created_at: Some(self.created_at),
                    member_identities: Some(self.members.encode()),
                    conversation_category: Some(self.conversation_category as i32),
                    conversation_visibility: Some(self.conversation_visibility as i32),
                }),
                last_update_at: self.last_update_at,
            }
        }
    }
}

mod group {
    use std::collections::HashSet;

    use crate::{
        common::{GroupIdentity, ThreemaId},
        data_backup::codec::{DataBackupDecodeErrorSource, InternalBackupDecoder, InternalBackupEncoder},
        model::{
            group::{
                Group, GroupNotificationTriggerPolicyOverride, GroupNotificationTriggerPolicyOverridePolicy,
                GroupWithProfilePicture,
            },
            profile::ProfilePicture,
        },
        protobuf::{
            self,
            d2d_sync::group::notification_trigger_policy_override::{
                self as protobuf_notification_override, policy as protobuf_notification_policy,
            },
        },
    };

    impl InternalBackupDecoder<protobuf::common::GroupIdentity> for GroupIdentity {
        fn decode(raw_message: protobuf::common::GroupIdentity) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::common::GroupIdentity {
                group_id,
                creator_identity,
            } = raw_message;

            Ok(Self {
                group_id,
                creator_identity: ThreemaId::try_from(creator_identity.as_str())?,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::common::GroupIdentity> for GroupIdentity {
        fn encode(self) -> protobuf::common::GroupIdentity {
            protobuf::common::GroupIdentity {
                group_id: self.group_id,
                creator_identity: self.creator_identity.to_string(),
            }
        }
    }

    impl InternalBackupDecoder<protobuf::d2d_sync::group::NotificationTriggerPolicyOverride>
        for Option<GroupNotificationTriggerPolicyOverride>
    {
        fn decode(
            raw_message: protobuf::d2d_sync::group::NotificationTriggerPolicyOverride,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            // Parse raw message.
            let protobuf::d2d_sync::group::NotificationTriggerPolicyOverride { r#override } = raw_message;

            // Parse override.
            Ok(match Self::expect_field(r#override, "override")? {
                protobuf_notification_override::Override::Default(_) => None,
                protobuf_notification_override::Override::Policy(
                    protobuf_notification_override::Policy { policy, expires_at },
                ) => {
                    let policy = match Self::parse_enum_variant(policy)? {
                        protobuf_notification_policy::NotificationTriggerPolicy::Mentioned => {
                            GroupNotificationTriggerPolicyOverridePolicy::Mentioned
                        },
                        protobuf_notification_policy::NotificationTriggerPolicy::Never => {
                            GroupNotificationTriggerPolicyOverridePolicy::Never
                        },
                    };
                    Some(GroupNotificationTriggerPolicyOverride { policy, expires_at })
                },
            })
        }
    }
    impl InternalBackupEncoder<protobuf::d2d_sync::group::NotificationTriggerPolicyOverride>
        for GroupNotificationTriggerPolicyOverride
    {
        fn encode(self) -> protobuf::d2d_sync::group::NotificationTriggerPolicyOverride {
            let policy = match self.policy {
                GroupNotificationTriggerPolicyOverridePolicy::Mentioned => {
                    protobuf_notification_policy::NotificationTriggerPolicy::Mentioned
                },
                GroupNotificationTriggerPolicyOverridePolicy::Never => {
                    protobuf_notification_policy::NotificationTriggerPolicy::Never
                },
            };
            protobuf::d2d_sync::group::NotificationTriggerPolicyOverride {
                r#override: Some(protobuf_notification_override::Override::Policy(
                    protobuf_notification_override::Policy {
                        policy: policy as i32,
                        expires_at: self.expires_at,
                    },
                )),
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::AugmentedGroup> for GroupWithProfilePicture {
        fn decode(
            raw_message: protobuf::data_backup::AugmentedGroup,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::AugmentedGroup {
                group,
                last_update_at,
                profile_picture,
            } = raw_message;

            let protobuf::d2d_sync::Group {
                group_identity,
                name,
                created_at,
                user_state,
                profile_picture: _profile_picture,
                member_identities,
                notification_trigger_policy_override,

                #[expect(deprecated, reason = "Must be handled until phased out")]
                    deprecated_notification_sound_policy_override: _deprecated_override,

                conversation_category,
                conversation_visibility,
            } = Self::expect_field(group, "group")?;

            Ok(Self {
                group: Group {
                    identity: GroupIdentity::decode(Self::expect_field(group_identity, "group_identity")?)?,
                    name: name.unwrap_or_default(),
                    created_at: Self::expect_field(created_at, "created_at")?,
                    last_update_at,
                    user_state: Self::expect_and_parse_enum_variant(user_state, "user_state")?,
                    members: HashSet::decode(Self::expect_field(member_identities, "member_identities")?)?,
                    notification_trigger_policy_override: notification_trigger_policy_override
                        .map(Option::decode)
                        .transpose()?
                        .flatten(),
                    conversation_category: Self::expect_and_parse_enum_variant(
                        conversation_category,
                        "conversation_category",
                    )?,
                    conversation_visibility: Self::expect_and_parse_enum_variant(
                        conversation_visibility,
                        "conversation_visibility",
                    )?,
                },
                profile_picture: profile_picture.map(ProfilePicture::decode).transpose()?,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::AugmentedGroup> for GroupWithProfilePicture {
        fn encode(self) -> protobuf::data_backup::AugmentedGroup {
            protobuf::data_backup::AugmentedGroup {
                group: Some(protobuf::d2d_sync::Group {
                    group_identity: Some(self.group.identity.encode()),
                    name: Some(self.group.name),
                    created_at: Some(self.group.created_at),
                    user_state: Some(self.group.user_state as i32),
                    profile_picture: None,
                    member_identities: Some(self.group.members.encode()),
                    notification_trigger_policy_override: self
                        .group
                        .notification_trigger_policy_override
                        .map(GroupNotificationTriggerPolicyOverride::encode),

                    #[expect(deprecated, reason = "Must be handled until phased out")]
                    deprecated_notification_sound_policy_override: Some(
                        protobuf::d2d_sync::group::DeprecatedNotificationSoundPolicyOverride {
                            r#override: None,
                        },
                    ),

                    conversation_category: Some(self.group.conversation_category as i32),
                    conversation_visibility: Some(self.group.conversation_visibility as i32),
                }),
                last_update_at: self.group.last_update_at,
                profile_picture: self.profile_picture.map(ProfilePicture::encode),
            }
        }
    }
}

mod poll {
    use std::collections::HashMap;

    use crate::{
        common::{ConversationId, ThreemaId},
        data_backup::{
            DataBackupDecodeErrorSource,
            codec::{InternalBackupDecoder, InternalBackupEncoder},
        },
        model::poll::{Poll, PollChoice, PollChoiceId, PollId, PollVote, PollVotes, PollVotesSummary},
        protobuf::{self, data_backup::poll::poll_votes as protobuf_poll_votes},
    };

    impl InternalBackupDecoder<protobuf_poll_votes::poll_votes_list::PollVote> for PollVote {
        fn decode(
            raw_message: protobuf_poll_votes::poll_votes_list::PollVote,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf_poll_votes::poll_votes_list::PollVote {
                choices,
                created_at,
                modified_at,
            } = raw_message;

            Ok(Self {
                choices: choices.into_iter().map(PollChoiceId).collect(),
                created_at,
                modified_at,
            })
        }
    }
    impl InternalBackupEncoder<protobuf_poll_votes::poll_votes_list::PollVote> for PollVote {
        fn encode(self) -> protobuf_poll_votes::poll_votes_list::PollVote {
            protobuf_poll_votes::poll_votes_list::PollVote {
                choices: self.choices.into_iter().map(|choice_id| choice_id.0).collect(),
                created_at: self.created_at,
                modified_at: self.modified_at,
            }
        }
    }

    impl InternalBackupDecoder<protobuf_poll_votes::PollVotesList> for HashMap<ThreemaId, PollVote> {
        fn decode(
            raw_message: protobuf_poll_votes::PollVotesList,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf_poll_votes::PollVotesList { participant_to_votes } = raw_message;

            participant_to_votes
                .into_iter()
                .map(|(participant, vote)| {
                    Ok::<_, DataBackupDecodeErrorSource>((
                        ThreemaId::try_from(participant.as_str())?,
                        PollVote::decode(vote)?,
                    ))
                })
                .collect::<Result<_, _>>()
        }
    }
    impl InternalBackupEncoder<protobuf_poll_votes::PollVotesList> for HashMap<ThreemaId, PollVote> {
        fn encode(self) -> protobuf_poll_votes::PollVotesList {
            protobuf_poll_votes::PollVotesList {
                participant_to_votes: self
                    .into_iter()
                    .map(|(participant, vote)| (participant.to_string(), vote.encode()))
                    .collect(),
            }
        }
    }

    impl InternalBackupDecoder<protobuf_poll_votes::PollVotesSummary> for PollVotesSummary {
        fn decode(
            raw_message: protobuf_poll_votes::PollVotesSummary,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf_poll_votes::PollVotesSummary {
                poll_choice_id_to_count,
                user_choices,
            } = raw_message;

            Ok(Self {
                summary: poll_choice_id_to_count
                    .into_iter()
                    .map(|(poll_choice_id, count)| {
                        let Ok(count) = count.try_into() else {
                            return Err(DataBackupDecodeErrorSource::InvalidMessage(
                                "Overflow: vote count".to_owned(),
                            ));
                        };

                        Ok((PollChoiceId(poll_choice_id), count))
                    })
                    .collect::<Result<_, _>>()?,
                user_choices: user_choices.into_iter().map(PollChoiceId).collect(),
            })
        }
    }
    impl InternalBackupEncoder<protobuf_poll_votes::PollVotesSummary> for PollVotesSummary {
        fn encode(self) -> protobuf_poll_votes::PollVotesSummary {
            protobuf_poll_votes::PollVotesSummary {
                poll_choice_id_to_count: self
                    .summary
                    .into_iter()
                    .map(|(poll_choice_id, count)| (poll_choice_id.0, u32::from(count)))
                    .collect(),
                user_choices: self
                    .user_choices
                    .into_iter()
                    .map(|poll_choice_id| poll_choice_id.0)
                    .collect(),
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::poll::PollVotes> for PollVotes {
        fn decode(
            raw_message: protobuf::data_backup::poll::PollVotes,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::poll::PollVotes { votes } = raw_message;

            Ok(match Self::expect_field(votes, "votes")? {
                protobuf_poll_votes::Votes::PollVotesList(poll_votes_list) => {
                    Self::Votes(HashMap::decode(poll_votes_list)?)
                },
                protobuf_poll_votes::Votes::PollVotesSummary(votes_summary) => {
                    Self::Summary(PollVotesSummary::decode(votes_summary)?)
                },
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::poll::PollVotes> for PollVotes {
        fn encode(self) -> protobuf::data_backup::poll::PollVotes {
            protobuf::data_backup::poll::PollVotes {
                votes: Some(match self {
                    Self::Votes(votes) => protobuf_poll_votes::Votes::PollVotesList(votes.encode()),
                    Self::Summary(vote_counts) => {
                        protobuf_poll_votes::Votes::PollVotesSummary(vote_counts.encode())
                    },
                }),
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::poll::PollChoice> for PollChoice {
        fn decode(
            raw_message: protobuf::data_backup::poll::PollChoice,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::poll::PollChoice { id, description } = raw_message;

            Ok(Self {
                id: PollChoiceId(id),
                description,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::poll::PollChoice> for PollChoice {
        fn encode(self) -> protobuf::data_backup::poll::PollChoice {
            protobuf::data_backup::poll::PollChoice {
                id: self.id.0,
                description: self.description,
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::Poll> for Poll {
        fn decode(raw_message: protobuf::data_backup::Poll) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::Poll {
                poll_id,
                conversation,
                creator_identity,
                created_at,
                state,
                description,
                answer_type,
                announce_type,
                display_mode,
                choices,
                votes,
            } = raw_message;

            Ok(Self {
                id: PollId(poll_id),
                conversation: ConversationId::decode(Self::expect_field(conversation, "conversation")?)?,
                creator_identity: ThreemaId::try_from(creator_identity.as_str())?,
                created_at,
                state: Self::parse_enum_variant(state)?,
                description,
                answer_type: Self::parse_enum_variant(answer_type)?,
                announce_type: Self::parse_enum_variant(announce_type)?,
                display_mode: Self::parse_enum_variant(display_mode)?,
                choices: choices
                    .into_iter()
                    .map(PollChoice::decode)
                    .collect::<Result<_, _>>()?,
                votes: PollVotes::decode(Self::expect_field(votes, "votes")?)?,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::Poll> for Poll {
        fn encode(self) -> protobuf::data_backup::Poll {
            protobuf::data_backup::Poll {
                poll_id: self.id.0,
                conversation: Some(self.conversation.encode()),
                creator_identity: self.creator_identity.to_string(),
                created_at: self.created_at,
                state: self.state.into(),
                description: self.description,
                answer_type: self.answer_type.into(),
                announce_type: self.announce_type.into(),
                display_mode: self.display_mode.into(),
                choices: self.choices.into_iter().map(PollChoice::encode).collect(),
                votes: Some(self.votes.encode()),
            }
        }
    }
}

mod message {
    use duplicate::duplicate_item;

    use crate::{
        common::{
            ConversationId, MessageId, MessageOverrides, MessageOverridesDeliveryReceiptPolicy, ThreemaId,
        },
        data_backup::{
            DataBackupDecodeErrorSource,
            codec::{InternalBackupDecoder, InternalBackupEncoder},
        },
        model::{
            message::{
                DeletedMessageState, EmojiReaction, IncomingMessageDirection, LocationMessageState,
                MessageDirection, MessageReaction, MessageStarMarker, MessageState, MessageStateBody,
                MessageStateBodyType, MessageText, MessageTextRevision, OutgoingMessageDirection, PollClose,
                PollOpen, TextMessageState,
            },
            poll::PollId,
        },
        protobuf::{
            self, data_backup::incoming_message_direction::message_overrides as protobuf_message_overrides,
        },
    };

    impl InternalBackupDecoder<protobuf::data_backup::MessageReaction> for MessageReaction {
        fn decode(
            raw_message: protobuf::data_backup::MessageReaction,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::MessageReaction {
                created_at,
                creator_identity,
                emoji,
            } = raw_message;
            Ok(Self {
                created_at,
                creator_identity: ThreemaId::try_from(creator_identity.as_str())?,
                emoji: EmojiReaction { codepoint: emoji },
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::MessageReaction> for MessageReaction {
        fn encode(self) -> protobuf::data_backup::MessageReaction {
            protobuf::data_backup::MessageReaction {
                created_at: self.created_at,
                creator_identity: self.creator_identity.to_string(),
                emoji: self.emoji.codepoint,
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::MessageTextRevision> for MessageTextRevision {
        fn decode(
            raw_message: protobuf::data_backup::MessageTextRevision,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::MessageTextRevision { created_at, text } = raw_message;

            Ok(Self {
                created_at,
                message: MessageText { text },
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::MessageTextRevision> for MessageTextRevision {
        fn encode(self) -> protobuf::data_backup::MessageTextRevision {
            protobuf::data_backup::MessageTextRevision {
                created_at: self.created_at,
                text: self.message.text,
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::TextMessageState> for TextMessageState {
        fn decode(
            raw_message: protobuf::data_backup::TextMessageState,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::TextMessageState {
                original_text,
                revisions,
            } = raw_message;

            Ok(Self {
                original: MessageText { text: original_text },
                revisions: revisions
                    .into_iter()
                    .map(MessageTextRevision::decode)
                    .collect::<Result<_, _>>()?,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::TextMessageState> for TextMessageState {
        fn encode(self) -> protobuf::data_backup::TextMessageState {
            protobuf::data_backup::TextMessageState {
                original_text: self.original.text,
                revisions: self
                    .revisions
                    .into_iter()
                    .map(MessageTextRevision::encode)
                    .collect(),
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::LocationMessageState> for LocationMessageState {
        fn decode(
            raw_message: protobuf::data_backup::LocationMessageState,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::LocationMessageState {
                latitude,
                longitude,
                accuracy_m,
                name,
                address,
            } = raw_message;

            Ok(Self {
                latitude,
                longitude,
                accuracy_m,
                name: Self::expect_field(name, "name")?,
                address: Self::expect_field(address, "address")?,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::LocationMessageState> for LocationMessageState {
        fn encode(self) -> protobuf::data_backup::LocationMessageState {
            protobuf::data_backup::LocationMessageState {
                latitude: self.latitude,
                longitude: self.longitude,
                accuracy_m: self.accuracy_m,
                name: Some(self.name),
                address: Some(self.address),
            }
        }
    }

    #[duplicate_item(
        poll_message;
        [ PollOpen ];
        [ PollClose ];
    )]
    impl InternalBackupDecoder<protobuf::data_backup::poll_message> for poll_message {
        fn decode(
            raw_message: protobuf::data_backup::poll_message,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::poll_message { poll_id } = raw_message;

            Ok(Self { id: PollId(poll_id) })
        }
    }
    #[duplicate_item(
        poll_message;
        [ PollOpen ];
        [ PollClose ];
    )]
    impl InternalBackupEncoder<protobuf::data_backup::poll_message> for poll_message {
        fn encode(self) -> protobuf::data_backup::poll_message {
            protobuf::data_backup::poll_message { poll_id: self.id.0 }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::DeletedMessageState> for DeletedMessageState {
        fn decode(
            raw_message: protobuf::data_backup::DeletedMessageState,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::DeletedMessageState {
                previous_type,
                deleted_at,
            } = raw_message;

            Ok(Self {
                previous_message_type: match Self::parse_enum_variant(previous_type)? {
                    protobuf::data_backup::MessageStateBodyType::Text => MessageStateBodyType::Text,
                    protobuf::data_backup::MessageStateBodyType::Location => MessageStateBodyType::Location,
                    protobuf::data_backup::MessageStateBodyType::Pollopen => MessageStateBodyType::PollOpen,
                    protobuf::data_backup::MessageStateBodyType::Pollclose => MessageStateBodyType::PollClose,
                    protobuf::data_backup::MessageStateBodyType::Deleted => MessageStateBodyType::Deleted,
                },
                deleted_at,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::DeletedMessageState> for DeletedMessageState {
        fn encode(self) -> protobuf::data_backup::DeletedMessageState {
            protobuf::data_backup::DeletedMessageState {
                previous_type: self.previous_message_type as i32,
                deleted_at: self.deleted_at,
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::incoming_message_direction::MessageOverrides>
        for MessageOverrides
    {
        fn decode(
            raw_message: protobuf::data_backup::incoming_message_direction::MessageOverrides,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::incoming_message_direction::MessageOverrides {
                delivery_receipt_policy,
            } = raw_message;

            Ok(Self {
                delivery_receipt_policy: match Self::parse_enum_variant(delivery_receipt_policy)? {
                    protobuf_message_overrides::DeliveryReceiptPolicy::DefaultDeliveryReceiptPolicy => {
                        MessageOverridesDeliveryReceiptPolicy::Default
                    },
                    protobuf_message_overrides::DeliveryReceiptPolicy::DontSendDeliveryReceipt => {
                        MessageOverridesDeliveryReceiptPolicy::DontSend
                    },
                },
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::incoming_message_direction::MessageOverrides>
        for MessageOverrides
    {
        fn encode(self) -> protobuf::data_backup::incoming_message_direction::MessageOverrides {
            protobuf::data_backup::incoming_message_direction::MessageOverrides {
                delivery_receipt_policy: match self.delivery_receipt_policy {
                    MessageOverridesDeliveryReceiptPolicy::Default => {
                        protobuf_message_overrides::DeliveryReceiptPolicy::DefaultDeliveryReceiptPolicy
                    },
                    MessageOverridesDeliveryReceiptPolicy::DontSend => {
                        protobuf_message_overrides::DeliveryReceiptPolicy::DontSendDeliveryReceipt
                    },
                }
                .into(),
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::IncomingMessageDirection> for IncomingMessageDirection {
        fn decode(
            raw_message: protobuf::data_backup::IncomingMessageDirection,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::IncomingMessageDirection {
                received_at,
                overrides,
            } = raw_message;

            Ok(Self {
                received_at,
                overrides: MessageOverrides::decode(Self::expect_field(overrides, "overrides")?)?,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::IncomingMessageDirection> for IncomingMessageDirection {
        fn encode(self) -> protobuf::data_backup::IncomingMessageDirection {
            protobuf::data_backup::IncomingMessageDirection {
                received_at: self.received_at,
                overrides: Some(self.overrides.encode()),
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::OutgoingMessageDirection> for OutgoingMessageDirection {
        fn decode(
            raw_message: protobuf::data_backup::OutgoingMessageDirection,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::OutgoingMessageDirection {
                sent_at,
                delivered_at,
            } = raw_message;

            Ok(Self {
                sent_at,
                delivered_at,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::OutgoingMessageDirection> for OutgoingMessageDirection {
        fn encode(self) -> protobuf::data_backup::OutgoingMessageDirection {
            protobuf::data_backup::OutgoingMessageDirection {
                sent_at: self.sent_at,
                delivered_at: self.delivered_at,
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::MessageDirection> for MessageDirection {
        fn decode(
            raw_message: protobuf::data_backup::MessageDirection,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::MessageDirection { direction } = raw_message;

            Ok(match Self::expect_field(direction, "direction")? {
                protobuf::data_backup::message_direction::Direction::Incoming(incoming_message_direction) => {
                    Self::Incoming(IncomingMessageDirection::decode(incoming_message_direction)?)
                },
                protobuf::data_backup::message_direction::Direction::Outgoing(outgoing_message_direction) => {
                    Self::Outgoing(OutgoingMessageDirection::decode(outgoing_message_direction)?)
                },
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::MessageDirection> for MessageDirection {
        fn encode(self) -> protobuf::data_backup::MessageDirection {
            protobuf::data_backup::MessageDirection {
                direction: match self {
                    Self::Incoming(incoming_message_direction) => {
                        Some(protobuf::data_backup::message_direction::Direction::Incoming(
                            incoming_message_direction.encode(),
                        ))
                    },
                    Self::Outgoing(outgoing_message_direction) => {
                        Some(protobuf::data_backup::message_direction::Direction::Outgoing(
                            outgoing_message_direction.encode(),
                        ))
                    },
                },
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::MessageStateBody> for MessageStateBody {
        fn decode(
            raw_message: protobuf::data_backup::MessageStateBody,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::MessageStateBody { body } = raw_message;

            Ok(match Self::expect_field(body, "body")? {
                protobuf::data_backup::message_state_body::Body::Text(text_message_state) => {
                    Self::Text(TextMessageState::decode(text_message_state)?)
                },
                protobuf::data_backup::message_state_body::Body::Location(location_message) => {
                    Self::Location(LocationMessageState::decode(location_message)?)
                },
                protobuf::data_backup::message_state_body::Body::PollOpen(poll_open_message) => {
                    Self::PollOpen(PollOpen::decode(poll_open_message)?)
                },
                protobuf::data_backup::message_state_body::Body::PollClose(poll_close_message) => {
                    Self::PollClose(PollClose::decode(poll_close_message)?)
                },
                protobuf::data_backup::message_state_body::Body::Deleted(deleted_message) => {
                    Self::Deleted(DeletedMessageState::decode(deleted_message)?)
                },
            })
        }
    }

    impl InternalBackupEncoder<protobuf::data_backup::MessageStateBody> for MessageStateBody {
        fn encode(self) -> protobuf::data_backup::MessageStateBody {
            protobuf::data_backup::MessageStateBody {
                body: Some(match self {
                    Self::Text(text_message_state) => {
                        protobuf::data_backup::message_state_body::Body::Text(text_message_state.encode())
                    },
                    Self::Location(location_message_state) => {
                        protobuf::data_backup::message_state_body::Body::Location(
                            location_message_state.encode(),
                        )
                    },
                    Self::PollOpen(poll_open) => {
                        protobuf::data_backup::message_state_body::Body::PollOpen(poll_open.encode())
                    },
                    Self::PollClose(poll_close) => {
                        protobuf::data_backup::message_state_body::Body::PollClose(poll_close.encode())
                    },
                    Self::Deleted(deleted_message_state) => {
                        protobuf::data_backup::message_state_body::Body::Deleted(
                            deleted_message_state.encode(),
                        )
                    },
                }),
            }
        }
    }

    impl InternalBackupDecoder<protobuf::data_backup::MessageState> for MessageState {
        fn decode(
            raw_message: protobuf::data_backup::MessageState,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let protobuf::data_backup::MessageState {
                conversation_id,
                creator_identity,
                message_id,
                created_at,
                direction,
                read_at,
                star_marker,
                reactions,
                body,
            } = raw_message;

            Ok(Self {
                conversation_id: ConversationId::decode(Self::expect_field(
                    conversation_id,
                    "conversation_id",
                )?)?,
                creator_identity: ThreemaId::try_from(creator_identity.as_str())?,
                message_id: MessageId(message_id),
                created_at,
                direction: MessageDirection::decode(Self::expect_field(direction, "direction")?)?,
                read_at,
                star_marker: match Self::parse_enum_variant(star_marker)? {
                    protobuf::data_backup::MessageStarMarker::NoMarker => MessageStarMarker::None,
                    protobuf::data_backup::MessageStarMarker::Star => MessageStarMarker::Star,
                },
                reactions: reactions
                    .into_iter()
                    .map(MessageReaction::decode)
                    .collect::<Result<Vec<_>, _>>()?,
                body: MessageStateBody::decode(Self::expect_field(body, "body")?)?,
            })
        }
    }
    impl InternalBackupEncoder<protobuf::data_backup::MessageState> for MessageState {
        fn encode(self) -> protobuf::data_backup::MessageState {
            protobuf::data_backup::MessageState {
                conversation_id: Some(ConversationId::encode(self.conversation_id)),
                creator_identity: self.creator_identity.to_string(),
                message_id: self.message_id.0,
                created_at: self.created_at,
                direction: Some(MessageDirection::encode(self.direction)),
                read_at: self.read_at,
                star_marker: match self.star_marker {
                    MessageStarMarker::None => protobuf::data_backup::MessageStarMarker::NoMarker,
                    MessageStarMarker::Star => protobuf::data_backup::MessageStarMarker::Star,
                }
                .into(),
                reactions: self.reactions.into_iter().map(MessageReaction::encode).collect(),
                body: Some(MessageStateBody::encode(self.body)),
            }
        }
    }
}

mod user {
    use std::collections::HashSet;

    use tracing::warn;

    use crate::{
        common::{ChatServerGroup, ThreemaId, keys::ClientKey},
        data_backup::{
            codec::{DataBackupDecodeErrorSource, InternalBackupDecoder, InternalBackupEncoder},
            common::UserData,
        },
        model::{
            profile::ProfilePicture,
            settings::Settings,
            user_profile::{
                IdentityData, IdentityLinkEmailAddress, IdentityLinkPhoneNumber, ProfilePictureSharePolicy,
                UserProfile,
            },
        },
        protobuf::{
            self,
            d2d_sync::{self, user_profile},
            data_backup,
        },
        utils::debug::Name as _,
        work::properties::WorkAvailabilityStatus,
    };

    impl InternalBackupDecoder<user_profile::ProfilePictureShareWith> for ProfilePictureSharePolicy {
        fn decode(
            raw_message: user_profile::ProfilePictureShareWith,
        ) -> Result<Self, DataBackupDecodeErrorSource> {
            let user_profile::ProfilePictureShareWith { policy } = raw_message;

            Ok(match Self::expect_field(policy, "policy")? {
                user_profile::profile_picture_share_with::Policy::Nobody(_) => Self::Nobody,
                user_profile::profile_picture_share_with::Policy::Everyone(_) => Self::Everyone,
                user_profile::profile_picture_share_with::Policy::AllowList(identities) => {
                    Self::AllowList(HashSet::decode(identities)?)
                },
            })
        }
    }
    impl InternalBackupEncoder<user_profile::ProfilePictureShareWith> for ProfilePictureSharePolicy {
        fn encode(self) -> user_profile::ProfilePictureShareWith {
            let policy = match self {
                Self::Nobody => {
                    user_profile::profile_picture_share_with::Policy::Nobody(protobuf::common::Unit {})
                },
                Self::Everyone => {
                    user_profile::profile_picture_share_with::Policy::Everyone(protobuf::common::Unit {})
                },
                Self::AllowList(identities) => {
                    user_profile::profile_picture_share_with::Policy::AllowList(identities.encode())
                },
            };

            user_profile::ProfilePictureShareWith { policy: Some(policy) }
        }
    }

    impl InternalBackupEncoder<user_profile::identity_links::IdentityLink> for IdentityLinkEmailAddress {
        fn encode(self) -> user_profile::identity_links::IdentityLink {
            user_profile::identity_links::IdentityLink {
                // The protocol supports a description that is not used in the apps.
                description: String::new(),
                r#type: Some(user_profile::identity_links::identity_link::Type::Email(
                    match self {
                        Self::EmailAddress(email_address) => email_address,
                        Self::Unknown => String::new(),
                    },
                )),
            }
        }
    }
    impl InternalBackupEncoder<user_profile::identity_links::IdentityLink> for IdentityLinkPhoneNumber {
        fn encode(self) -> user_profile::identity_links::IdentityLink {
            user_profile::identity_links::IdentityLink {
                // The protocol supports a description that is not used in the apps.
                description: String::new(),
                r#type: Some(user_profile::identity_links::identity_link::Type::PhoneNumber(
                    match self {
                        Self::PhoneNumber(phone_number) => phone_number,
                        Self::Unknown => String::new(),
                    },
                )),
            }
        }
    }

    impl InternalBackupDecoder<data_backup::IdentityData> for IdentityData {
        fn decode(raw_message: data_backup::IdentityData) -> Result<Self, DataBackupDecodeErrorSource> {
            let data_backup::IdentityData {
                identity,
                ck,
                csp_server_group,
            } = raw_message;

            Ok(Self {
                user_identity: ThreemaId::try_from(identity.as_str())?,
                client_key: ClientKey::from(TryInto::<[u8; ClientKey::LENGTH]>::try_into(ck).map_err(
                    |_| {
                        DataBackupDecodeErrorSource::InvalidMessage(
                            "Client key must have 32 bytes".to_owned(),
                        )
                    },
                )?),
                csp_server_group: ChatServerGroup::try_from(csp_server_group.as_str()).map_err(|_| {
                    DataBackupDecodeErrorSource::InvalidMessage(format!("Invalid {}", ChatServerGroup::NAME))
                })?,
            })
        }
    }
    impl InternalBackupEncoder<data_backup::IdentityData> for IdentityData {
        fn encode(self) -> data_backup::IdentityData {
            data_backup::IdentityData {
                identity: self.user_identity.to_string(),
                ck: self.client_key.as_bytes().to_vec(),
                csp_server_group: self.csp_server_group.as_str().to_owned(),
            }
        }
    }

    impl InternalBackupDecoder<data_backup::UserProfile> for UserProfile {
        fn decode(raw_message: data_backup::UserProfile) -> Result<Self, DataBackupDecodeErrorSource> {
            let data_backup::UserProfile {
                nickname,
                profile_picture,
                profile_picture_share_with,
                identity_links,
                work_availability_status,
            } = raw_message;

            // Decode identity links.
            let (linked_email_address, linked_phone_number) = {
                let user_profile::IdentityLinks {
                    links: identity_links,
                } = Self::expect_field(identity_links, "identity_links")?;

                let mut linked_email_address = None;
                let mut linked_phone_number = None;
                for user_profile::identity_links::IdentityLink {
                    description: _description,
                    r#type,
                } in identity_links
                {
                    match r#type {
                        Some(user_profile::identity_links::identity_link::Type::PhoneNumber(
                            phone_number,
                        )) => {
                            if linked_phone_number.is_some() {
                                warn!("Skipping unsupported secondary linked phone number");
                            } else {
                                linked_phone_number = Some(if phone_number.is_empty() {
                                    IdentityLinkPhoneNumber::Unknown
                                } else {
                                    IdentityLinkPhoneNumber::PhoneNumber(phone_number)
                                });
                            }
                        },
                        Some(user_profile::identity_links::identity_link::Type::Email(email_address)) => {
                            if linked_email_address.is_some() {
                                warn!("Skipping unsupported secondary linked email address");
                            } else {
                                linked_email_address = Some(if email_address.is_empty() {
                                    IdentityLinkEmailAddress::Unknown
                                } else {
                                    IdentityLinkEmailAddress::EmailAddress(email_address)
                                });
                            }
                        },
                        None => {},
                    }
                }

                (linked_email_address, linked_phone_number)
            };

            Ok(Self {
                nickname: Self::expect_field(nickname, "nickname")?,
                profile_picture_share_policy: ProfilePictureSharePolicy::decode(Self::expect_field(
                    profile_picture_share_with,
                    "profile_picture_share_with",
                )?)?,
                profile_picture: profile_picture.map(ProfilePicture::decode).transpose()?,
                linked_phone_number,
                linked_email_address,
                work_availability_status: work_availability_status
                    .map(WorkAvailabilityStatus::decode)
                    .transpose()?,
            })
        }
    }
    impl InternalBackupEncoder<data_backup::UserProfile> for UserProfile {
        fn encode(self) -> data_backup::UserProfile {
            // Encode identity links.
            let identity_links = {
                let mut identity_links = Vec::new();
                if let Some(linked_email_address) = self.linked_email_address {
                    identity_links.push(linked_email_address.encode());
                }
                if let Some(linked_phone_number) = self.linked_phone_number {
                    identity_links.push(linked_phone_number.encode());
                }
                Some(user_profile::IdentityLinks {
                    links: identity_links,
                })
            };

            data_backup::UserProfile {
                nickname: Some(self.nickname),
                profile_picture: self.profile_picture.map(ProfilePicture::encode),
                profile_picture_share_with: Some(self.profile_picture_share_policy.encode()),
                identity_links,
                work_availability_status: self.work_availability_status.map(WorkAvailabilityStatus::into),
            }
        }
    }

    impl InternalBackupDecoder<d2d_sync::Settings> for Settings {
        fn decode(raw_message: d2d_sync::Settings) -> Result<Self, DataBackupDecodeErrorSource> {
            let d2d_sync::Settings {
                #[expect(deprecated, reason = "Must be handled until phased out")]
                    deprecated_contact_sync_policy: _deprecated_contact_sync_policy,

                unknown_contact_policy,
                read_receipt_policy,
                typing_indicator_policy,
                o2o_call_policy,
                o2o_call_connection_policy,
                o2o_call_video_policy,
                group_call_policy,
                screenshot_policy,

                #[expect(deprecated, reason = "Must be handled until phased out")]
                    deprecated_keyboard_data_collection_policy: _deprecated_keyboard_data_collection_policy,

                blocked_identities,
                exclude_from_sync_identities,
            } = raw_message;

            Ok(Self {
                unknown_contact_policy: Self::expect_and_parse_enum_variant(
                    unknown_contact_policy,
                    "unknown_contact_policy",
                )?,
                read_receipt_policy: Self::expect_and_parse_enum_variant(
                    read_receipt_policy,
                    "read_receipt_policy",
                )?,
                typing_indicator_policy: Self::expect_and_parse_enum_variant(
                    typing_indicator_policy,
                    "typing_indicator_policy",
                )?,
                o2o_call_policy: Self::expect_and_parse_enum_variant(o2o_call_policy, "o2o_call policy")?,
                o2o_call_connection_policy: Self::expect_and_parse_enum_variant(
                    o2o_call_connection_policy,
                    "o2o_call_connection_policy",
                )?,
                o2o_call_video_policy: Self::expect_and_parse_enum_variant(
                    o2o_call_video_policy,
                    "o2o_call_video_policy",
                )?,
                group_call_policy: Self::expect_and_parse_enum_variant(
                    group_call_policy,
                    "group_call_policy",
                )?,
                screenshot_policy: Self::expect_and_parse_enum_variant(
                    screenshot_policy,
                    "screenshot_policy",
                )?,
                blocked_identities: Self::expect_field(blocked_identities, "blocked_identities")?
                    .identities
                    .into_iter()
                    .map(|identity| ThreemaId::try_from(identity.as_str()))
                    .collect::<Result<_, _>>()?,

                exclude_from_sync_identities: Self::expect_field(
                    exclude_from_sync_identities,
                    "exclude_from_sync_identities",
                )?
                .identities
                .into_iter()
                .map(|identity| ThreemaId::try_from(identity.as_str()))
                .collect::<Result<_, _>>()?,
            })
        }
    }
    impl InternalBackupEncoder<d2d_sync::Settings> for Settings {
        fn encode(self) -> d2d_sync::Settings {
            d2d_sync::Settings {
                #[expect(deprecated, reason = "Must be handled until phased out")]
                deprecated_contact_sync_policy: Some(
                    d2d_sync::settings::DeprecatedContactSyncPolicy::NotSynced as i32,
                ),
                unknown_contact_policy: Some(self.unknown_contact_policy as i32),
                read_receipt_policy: Some(self.read_receipt_policy as i32),
                typing_indicator_policy: Some(self.typing_indicator_policy as i32),
                o2o_call_policy: Some(self.o2o_call_policy as i32),
                o2o_call_connection_policy: Some(self.o2o_call_connection_policy as i32),
                o2o_call_video_policy: Some(self.o2o_call_video_policy as i32),
                group_call_policy: Some(self.group_call_policy as i32),
                screenshot_policy: Some(self.screenshot_policy as i32),

                #[expect(deprecated, reason = "Must be handled until phased out")]
                deprecated_keyboard_data_collection_policy: Some(
                    d2d_sync::settings::DeprecatedKeyboardDataCollectionPolicy::AllowDataCollection as i32,
                ),

                blocked_identities: Some(self.blocked_identities.encode()),
                exclude_from_sync_identities: Some(self.exclude_from_sync_identities.encode()),
            }
        }
    }

    impl InternalBackupDecoder<data_backup::UserData> for UserData {
        fn decode(raw_message: data_backup::UserData) -> Result<Self, DataBackupDecodeErrorSource> {
            let data_backup::UserData {
                identity_data,
                user_profile,
                settings,
            } = raw_message;

            Ok(Self {
                identity_data: IdentityData::decode(Self::expect_field(identity_data, "identity_data")?)?,
                user_profile: UserProfile::decode(Self::expect_field(user_profile, "user_profile")?)?,
                settings: Settings::decode(Self::expect_field(settings, "settings")?)?,
            })
        }
    }
    impl InternalBackupEncoder<data_backup::UserData> for UserData {
        fn encode(self) -> data_backup::UserData {
            data_backup::UserData {
                identity_data: Some(self.identity_data.encode()),
                user_profile: Some(self.user_profile.encode()),
                settings: Some(self.settings.encode()),
            }
        }
    }
}

pub(crate) mod container {
    use duplicate::duplicate_item;
    use prost::Message as _;

    use crate::{
        data_backup::{
            DataBackupCreateError, DataBackupCreateInternalErrorCause, DataBackupDecodeErrorSource,
            DataBackupEncodeErrorSource, DataBackupRestoreError,
            codec::{InternalBackupDecoder, InternalBackupEncoder},
            common::UserData,
        },
        model::{
            contact::ContactWithProfilePictures, distribution_list::DistributionList,
            group::GroupWithProfilePicture, message::MessageState, poll::Poll,
        },
        protobuf,
        utils::debug::Name,
    };

    /// Decode a variable length item.
    ///
    /// Note: This trait requires that the data was already sized to the correct length and stripped of the
    /// u32 little endian length delimiter.
    pub(crate) trait ContainerDecoder: Sized {
        fn decode(bytes: &[u8]) -> Result<Self, DataBackupRestoreError>;
    }
    #[duplicate_item(
        destination_type               decoded_type                                           ;
        [ UserData                   ] [ data_backup::UserData                               ];
        [ ContactWithProfilePictures ] [ data_backup::AugmentedContact                       ];
        [ DistributionList           ] [ d2d_join::essential_data::AugmentedDistributionList ];
        [ GroupWithProfilePicture    ] [ data_backup::AugmentedGroup                         ];
        [ MessageState               ] [ data_backup::MessageState                           ];
        [ Poll                       ] [ data_backup::Poll                                   ];
    )]
    impl ContainerDecoder for destination_type {
        fn decode(bytes: &[u8]) -> Result<Self, DataBackupRestoreError> {
            InternalBackupDecoder::decode(protobuf::decoded_type::decode(bytes).map_err(|error| {
                DataBackupRestoreError::DecodingFailed {
                    name: Self::NAME,
                    source: DataBackupDecodeErrorSource::from(error),
                }
            })?)
            .map_err(|error| DataBackupRestoreError::DecodingFailed {
                name: Self::NAME,
                source: error,
            })
        }
    }

    /// Encode a variable length item, placing a u32 little endian ahead to determine the length.
    pub(crate) trait ContainerEncoder: Name {
        fn encode_u32_le_delimited(self) -> Result<Vec<u8>, DataBackupCreateError>;
    }

    #[duplicate_item(
        source_type                   ;
        [ UserData                   ];
        [ ContactWithProfilePictures ];
        [ DistributionList           ];
        [ GroupWithProfilePicture    ];
        [ MessageState               ];
        [ Poll                       ];
    )]
    impl ContainerEncoder for source_type {
        fn encode_u32_le_delimited(self) -> Result<Vec<u8>, DataBackupCreateError> {
            use crate::utils::protobuf::DelimitedMessageU32LittleEndian as _;

            InternalBackupEncoder::encode(self)
                .encode_u32_le_delimited_to_vec()
                .map_err(|_| {
                    DataBackupCreateError::InternalError(DataBackupCreateInternalErrorCause::EncodingFailed {
                        name: Self::NAME,
                        source: DataBackupEncodeErrorSource::Other("Contained data length exceeds a u32"),
                    })
                })
        }
    }
}

Messung
CHZG
829810093

¤ Dauer der Verarbeitung: 0.46 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.