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

backup: Be stricter about recipient kinds

This commit is contained in:
Jordan Rose 2024-09-04 17:22:03 -07:00
parent 358164c537
commit 6529c3d189
11 changed files with 209 additions and 81 deletions

View File

@ -6,7 +6,7 @@
use std::fmt::Debug;
use crate::backup::frame::RecipientId;
use crate::backup::method::{Lookup, LookupPair};
use crate::backup::method::LookupPair;
use crate::backup::recipient::DestinationKind;
use crate::backup::time::Timestamp;
use crate::backup::TryFromWith;
@ -58,8 +58,12 @@ pub struct CallId(u64);
pub enum CallError {
/// call starter {0:?} not found,
UnknownCallStarter(RecipientId),
/// call starter {0:?} is a {1:?}, not a contact or self
InvalidCallStarter(RecipientId, DestinationKind),
/// no record for ringer {0:?}
NoRingerRecipient(RecipientId),
/// ringer {0:?} is a {1:?}, not a contact or self
InvalidRingerRecipient(RecipientId, DestinationKind),
/// no record for ad-hoc {0:?}
NoAdHocRecipient(RecipientId),
/// ad-hoc recipient {0:?} is not a call link
@ -193,7 +197,9 @@ impl TryFrom<proto::IndividualCall> for IndividualCall {
}
}
impl<C: Lookup<RecipientId, R>, R: Clone> TryFromWith<proto::GroupCall, C> for GroupCall<R> {
impl<C: LookupPair<RecipientId, DestinationKind, R>, R: Clone> TryFromWith<proto::GroupCall, C>
for GroupCall<R>
{
type Error = CallError;
fn try_from_with(call: proto::GroupCall, context: &C) -> Result<Self, Self::Error> {
@ -211,20 +217,26 @@ impl<C: Lookup<RecipientId, R>, R: Clone> TryFromWith<proto::GroupCall, C> for G
let started_call_recipient = startedCallRecipientId
.map(|id| {
let id = RecipientId(id);
context
.lookup(&id)
.ok_or(CallError::UnknownCallStarter(id))
.cloned()
let (&starter_kind, starter) = context
.lookup_pair(&id)
.ok_or(CallError::UnknownCallStarter(id))?;
if !starter_kind.is_individual() {
return Err(CallError::InvalidCallStarter(id, starter_kind));
}
Ok(starter.clone())
})
.transpose()?;
let ringer_recipient = ringerRecipientId
.map(|id| {
let id = RecipientId(id);
context
.lookup(&id)
.ok_or(CallError::NoRingerRecipient(id))
.cloned()
let (&ringer_kind, ringer) = context
.lookup_pair(&id)
.ok_or(CallError::NoRingerRecipient(id))?;
if !ringer_kind.is_individual() {
return Err(CallError::InvalidRingerRecipient(id, ringer_kind));
}
Ok(ringer.clone())
})
.transpose()?;
@ -423,18 +435,20 @@ pub(crate) mod test {
}
}
struct TestContext;
impl Lookup<RecipientId, DestinationKind> for TestContext {
fn lookup(&self, key: &RecipientId) -> Option<&DestinationKind> {
match key {
RecipientId(proto::Recipient::TEST_ID) => Some(&DestinationKind::Contact),
&TEST_CALL_LINK_RECIPIENT_ID => Some(&DestinationKind::CallLink),
_ => None,
impl CallLink {
pub(crate) fn from_proto_test_data() -> Self {
Self {
admin_approval: false,
root_key: TEST_CALL_LINK_ROOT_KEY,
admin_key: Some(TEST_CALL_LINK_ADMIN_KEY.to_vec()),
expiration: Timestamp::test_value(),
name: "".to_string(),
}
}
}
struct TestContext;
impl LookupPair<RecipientId, DestinationKind, RecipientId> for TestContext {
fn lookup_pair<'a>(
&'a self,
@ -480,7 +494,7 @@ pub(crate) mod test {
id: None,
state: GroupCallState::Accepted,
started_call_recipient: None,
ringer_recipient: Some(DestinationKind::Contact),
ringer_recipient: Some(RecipientId(proto::Recipient::TEST_ID)),
started_at: Timestamp::test_value(),
ended_at: Timestamp::test_value() + Duration::from_millis(1000),
read: true,
@ -488,11 +502,22 @@ pub(crate) mod test {
);
}
#[test_case(|x| x.ringerRecipientId = None => Ok(()); "no_ringer_id")]
#[test_case(
|x| x.ringerRecipientId = Some(NONEXISTENT_RECIPIENT.0) => Err(CallError::NoRingerRecipient(NONEXISTENT_RECIPIENT));
"wrong_wringer_id"
)]
#[test_case(|x| x.ringerRecipientId = None => Ok(()); "no ringer")]
#[test_case(|x| {
x.ringerRecipientId = Some(NONEXISTENT_RECIPIENT.0)
} => Err(CallError::NoRingerRecipient(NONEXISTENT_RECIPIENT)); "nonexistent ringer")]
#[test_case(|x| {
x.ringerRecipientId = Some(TEST_CALL_LINK_RECIPIENT_ID.0)
} => Err(CallError::InvalidRingerRecipient(TEST_CALL_LINK_RECIPIENT_ID, DestinationKind::CallLink)); "invalid ringer")]
#[test_case(|x| {
x.startedCallRecipientId = Some(proto::Recipient::TEST_ID)
} => Ok(()); "has call starter")]
#[test_case(|x| {
x.startedCallRecipientId = Some(NONEXISTENT_RECIPIENT.0)
} => Err(CallError::UnknownCallStarter(NONEXISTENT_RECIPIENT)); "nonexistent call starter")]
#[test_case(|x| {
x.startedCallRecipientId = Some(TEST_CALL_LINK_RECIPIENT_ID.0)
} => Err(CallError::InvalidCallStarter(TEST_CALL_LINK_RECIPIENT_ID, DestinationKind::CallLink)); "invalid call starter")]
#[test_case(|x| x.state = EnumOrUnknown::default() => Err(CallError::UnknownState); "unknown_state")]
fn group_call(modifier: fn(&mut proto::GroupCall)) -> Result<(), CallError> {
let mut call = proto::GroupCall::test_data();
@ -531,13 +556,7 @@ pub(crate) mod test {
fn valid_call_link() {
assert_eq!(
proto::CallLink::test_data().try_into(),
Ok(CallLink {
admin_approval: false,
root_key: TEST_CALL_LINK_ROOT_KEY,
admin_key: Some(TEST_CALL_LINK_ADMIN_KEY.to_vec()),
expiration: Timestamp::test_value(),
name: "".to_string(),
})
Ok(CallLink::from_proto_test_data())
);
}

View File

@ -17,7 +17,8 @@ use derive_where::derive_where;
use crate::backup::chat::chat_style::{ChatStyle, ChatStyleError, CustomColorId};
use crate::backup::file::{FilePointerError, MessageAttachmentError};
use crate::backup::frame::RecipientId;
use crate::backup::method::{Lookup, Method};
use crate::backup::method::{Lookup, LookupPair, Method};
use crate::backup::recipient::DestinationKind;
use crate::backup::serialize::{SerializeOrder, UnorderedList};
use crate::backup::sticker::MessageStickerError;
use crate::backup::time::{Duration, Timestamp};
@ -66,6 +67,8 @@ pub enum ChatError {
DuplicateId,
/// no record for {0:?}
NoRecipient(RecipientId),
/// cannot have a chat with recipient {0:?}, a {1:?}
InvalidRecipient(RecipientId, DestinationKind),
/// chat item: {0}
ChatItem(#[from] ChatItemError),
/// {0:?} already appeared
@ -81,6 +84,8 @@ pub enum ChatItemError {
NoChatForItem,
/// no record for chat item author {0:?}
AuthorNotFound(RecipientId),
/// chat item author {0:?} is a {1:?}
InvalidAuthor(RecipientId, DestinationKind),
/// ChatItem.item is a oneof but is empty
MissingItem,
/// text: {0}
@ -238,6 +243,8 @@ impl<Recipient: SerializeOrder> SerializeOrder for Reaction<Recipient> {
pub enum ReactionError {
/// unknown author {0:?}
AuthorNotFound(RecipientId),
/// author {0:?} was a {1:?}, not a contact or self
InvalidAuthor(RecipientId, DestinationKind),
/// "emoji" is an empty string
EmptyEmoji,
}
@ -292,6 +299,8 @@ pub enum DeliveryStatus {
pub enum OutgoingSendError {
/// send status has unknown recipient {0:?}
UnknownRecipient(RecipientId),
/// send status recipient {0:?} is a {1:?}, not a contact or self
InvalidRecipient(RecipientId, DestinationKind),
/// send status is missing
SendStatusMissing,
}
@ -318,7 +327,7 @@ impl std::fmt::Display for InvalidExpiration {
impl<
M: Method + ReferencedTypes,
C: Lookup<RecipientId, M::RecipientReference>
C: LookupPair<RecipientId, DestinationKind, M::RecipientReference>
+ Lookup<PinOrder, M::RecipientReference>
+ Lookup<CustomColorId, M::CustomColorReference>,
> TryFromWith<proto::Chat, C> for ChatData<M>
@ -339,10 +348,19 @@ impl<
special_fields: _,
} = value;
let recipient = RecipientId(recipientId);
let Some(recipient) = context.lookup(&recipient).cloned() else {
return Err(ChatError::NoRecipient(recipient));
let recipient_id = RecipientId(recipientId);
let Some((&kind, recipient)) = context.lookup_pair(&recipient_id) else {
return Err(ChatError::NoRecipient(recipient_id));
};
let recipient = match kind {
DestinationKind::Contact
| DestinationKind::Group
| DestinationKind::Self_
| DestinationKind::ReleaseNotes => Ok(recipient.clone()),
DestinationKind::DistributionList | DestinationKind::CallLink => {
Err(ChatError::InvalidRecipient(recipient_id, kind))
}
}?;
let pinned_order = NonZeroU32::new(pinnedOrder).map(PinOrder);
if let Some(pinned_order) = pinned_order {
@ -376,7 +394,7 @@ impl<
}
impl<
C: Lookup<RecipientId, M::RecipientReference> + AsRef<BackupMeta>,
C: LookupPair<RecipientId, DestinationKind, M::RecipientReference> + AsRef<BackupMeta>,
M: Method + ReferencedTypes,
> TryFromWith<proto::ChatItem, C> for ChatItemData<M>
{
@ -396,11 +414,23 @@ impl<
special_fields: _,
} = value;
let author = RecipientId(authorId);
let author_id = RecipientId(authorId);
let Some(author) = context.lookup(&author).cloned() else {
return Err(ChatItemError::AuthorNotFound(author));
let Some((&author_kind, author)) = context.lookup_pair(&author_id) else {
return Err(ChatItemError::AuthorNotFound(author_id));
};
let author = match author_kind {
DestinationKind::Contact | DestinationKind::Self_ | DestinationKind::ReleaseNotes => {
Ok(author.clone())
}
// Even update messages in groups are still attributed to self (if not a specific
// author)
DestinationKind::Group
| DestinationKind::DistributionList
| DestinationKind::CallLink => {
Err(ChatItemError::InvalidAuthor(author_id, author_kind))
}
}?;
let message = item
.ok_or(ChatItemError::MissingItem)?
@ -494,8 +524,8 @@ impl<
}
}
impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::chat_item::DirectionalDetails, C>
for Direction<R>
impl<R: Clone, C: LookupPair<RecipientId, DestinationKind, R>>
TryFromWith<proto::chat_item::DirectionalDetails, C> for Direction<R>
{
type Error = ChatItemError;
@ -538,7 +568,9 @@ impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::chat_item::Directio
}
}
}
impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::SendStatus, C> for OutgoingSend<R> {
impl<R: Clone, C: LookupPair<RecipientId, DestinationKind, R>> TryFromWith<proto::SendStatus, C>
for OutgoingSend<R>
{
type Error = OutgoingSendError;
fn try_from_with(item: proto::SendStatus, context: &C) -> Result<Self, Self::Error> {
@ -550,9 +582,13 @@ impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::SendStatus, C> for
} = item;
let recipient_id = RecipientId(recipientId);
let Some(recipient) = context.lookup(&recipient_id).cloned() else {
let Some((&kind, recipient)) = context.lookup_pair(&recipient_id) else {
return Err(OutgoingSendError::UnknownRecipient(recipient_id));
};
if !kind.is_individual() {
return Err(OutgoingSendError::InvalidRecipient(recipient_id, kind));
}
let recipient = recipient.clone();
let Some(status) = deliveryStatus else {
return Err(OutgoingSendError::SendStatusMissing);
@ -617,7 +653,7 @@ impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::SendStatus, C> for
}
impl<
R: Lookup<RecipientId, M::RecipientReference> + AsRef<BackupMeta>,
R: LookupPair<RecipientId, DestinationKind, M::RecipientReference> + AsRef<BackupMeta>,
M: Method + ReferencedTypes,
> TryFromWith<proto::chat_item::Item, R> for ChatItemMessage<M>
{
@ -660,7 +696,9 @@ impl<
}
}
impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::Reaction, C> for Reaction<R> {
impl<R: Clone, C: LookupPair<RecipientId, DestinationKind, R>> TryFromWith<proto::Reaction, C>
for Reaction<R>
{
type Error = ReactionError;
fn try_from_with(item: proto::Reaction, context: &C) -> Result<Self, Self::Error> {
@ -677,9 +715,13 @@ impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::Reaction, C> for Re
}
let author_id = RecipientId(authorId);
let Some(author) = context.lookup(&author_id).cloned() else {
let Some((&author_kind, author)) = context.lookup_pair(&author_id) else {
return Err(ReactionError::AuthorNotFound(author_id));
};
if !author_kind.is_individual() {
return Err(ReactionError::InvalidAuthor(author_id, author_kind));
}
let author = author.clone();
let sent_timestamp = Timestamp::from_millis(sentTimestamp, "Reaction.sentTimestamp");
@ -801,6 +843,12 @@ mod test {
Err(ChatError::DuplicatePinnedOrder(TestContext::DUPLICATE_PINNED_ORDER,));
"duplicate_pinned_order"
)]
#[test_case(|x| {
x.recipientId = TestContext::CALL_LINK_ID.0;
} => Err(ChatError::InvalidRecipient(TestContext::CALL_LINK_ID, DestinationKind::CallLink)); "call link chat")]
#[test_case(|x| {
x.recipientId = 0;
} => Err(ChatError::NoRecipient(RecipientId(0))); "unknown recipient")]
fn chat(modifier: fn(&mut proto::Chat)) -> Result<(), ChatError> {
let mut chat = proto::Chat::test_data();
modifier(&mut chat);
@ -833,6 +881,7 @@ mod test {
}
#[test_case(|x| x.authorId = 0xffff => Err(ChatItemError::AuthorNotFound(RecipientId(0xffff))); "unknown_author")]
#[test_case(|x| x.authorId = TestContext::GROUP_ID.0 => Err(ChatItemError::InvalidAuthor(TestContext::GROUP_ID, DestinationKind::Group)); "invalid author")]
#[test_case(|x| x.directionalDetails = None => Err(ChatItemError::NoDirection); "no_direction")]
#[test_case(|x| x.directionalDetails = Some(proto::chat_item::OutgoingMessageDetails::test_data().into()) => Ok(()); "outgoing_valid")]
#[test_case(|x| x.directionalDetails = Some(
@ -875,6 +924,18 @@ mod test {
.into(),
) => Err(ChatItemError::Outgoing(OutgoingSendError::UnknownRecipient(RecipientId(0xffff)))); "outgoing_unknown_recipient"
)]
#[test_case(
|x| x.directionalDetails = Some(
proto::chat_item::OutgoingMessageDetails {
sendStatus: vec![proto::SendStatus {
recipientId: TestContext::GROUP_ID.0,
..proto::SendStatus::test_data()
}],
..proto::chat_item::OutgoingMessageDetails::test_data()
}
.into(),
) => Err(ChatItemError::Outgoing(OutgoingSendError::InvalidRecipient(TestContext::GROUP_ID, DestinationKind::Group))); "outgoing invalid recipient"
)]
#[test_case(|x| x.directionalDetails = Some(proto::chat_item::DirectionlessMessageDetails::default().into()) => Err(ChatItemError::DirectionlessMessage); "directionless_non_update")]
#[test_case(|x| {
x.directionalDetails = Some(proto::chat_item::DirectionlessMessageDetails::default().into());
@ -905,7 +966,11 @@ mod test {
#[test_case(
|x| x.authorId = proto::Recipient::TEST_ID + 2 => Err(ReactionError::AuthorNotFound(RecipientId(proto::Recipient::TEST_ID + 2)));
"invalid_author_id"
"unknown author id"
)]
#[test_case(
|x| x.authorId = TestContext::GROUP_ID.0 => Err(ReactionError::InvalidAuthor(TestContext::GROUP_ID, DestinationKind::Group));
"invalid author id"
)]
fn reaction(modifier: fn(&mut proto::Reaction)) -> Result<(), ReactionError> {
let mut reaction = proto::Reaction::test_data();

View File

@ -407,8 +407,8 @@ mod test {
use super::*;
use crate::backup::chat::chat_style::Color;
use crate::backup::testutil::TestContext;
use crate::backup::method::Store;
use crate::backup::testutil::TestContext;
impl proto::ChatStyle {
fn test_data() -> Self {

View File

@ -7,7 +7,8 @@ use protobuf::EnumOrUnknown;
use crate::backup::chat::{ChatItemError, Reaction};
use crate::backup::file::{FilePointer, FilePointerError};
use crate::backup::frame::RecipientId;
use crate::backup::method::Lookup;
use crate::backup::method::LookupPair;
use crate::backup::recipient::DestinationKind;
use crate::backup::serialize::{SerializeOrder, UnorderedList};
use crate::backup::{TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
@ -45,7 +46,7 @@ pub enum ContactAttachmentError {
Avatar(FilePointerError),
}
impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::ContactMessage, C>
impl<R: Clone, C: LookupPair<RecipientId, DestinationKind, R>> TryFromWith<proto::ContactMessage, C>
for ContactMessage<R>
{
type Error = ChatItemError;
@ -172,9 +173,9 @@ mod test {
use test_case::test_case;
use super::*;
use crate::backup::testutil::TestContext;
use crate::backup::chat::ReactionError;
use crate::backup::recipient::FullRecipientData;
use crate::backup::testutil::TestContext;
impl proto::ContactMessage {
fn test_data() -> Self {

View File

@ -6,7 +6,8 @@
use crate::backup::chat::text::{MessageText, TextError};
use crate::backup::file::{MessageAttachment, MessageAttachmentError};
use crate::backup::frame::RecipientId;
use crate::backup::method::Lookup;
use crate::backup::method::LookupPair;
use crate::backup::recipient::DestinationKind;
use crate::backup::time::Timestamp;
use crate::backup::TryFromWith;
use crate::proto::backup as proto;
@ -47,6 +48,8 @@ pub struct QuotedAttachment {
pub enum QuoteError {
/// has unknown author {0:?}
AuthorNotFound(RecipientId),
/// author {0:?} is a {1:?}, not a contact or self
InvalidAuthor(RecipientId, DestinationKind),
/// "type" is unknown
TypeUnknown,
/// text error: {0}
@ -57,7 +60,9 @@ pub enum QuoteError {
AttachmentThumbnailWrongFlag(proto::message_attachment::Flag),
}
impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::Quote, C> for Quote<R> {
impl<R: Clone, C: LookupPair<RecipientId, DestinationKind, R>> TryFromWith<proto::Quote, C>
for Quote<R>
{
type Error = QuoteError;
fn try_from_with(item: proto::Quote, context: &C) -> Result<Self, Self::Error> {
@ -71,9 +76,21 @@ impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::Quote, C> for Quote
} = item;
let author_id = RecipientId(authorId);
let Some(author) = context.lookup(&author_id).cloned() else {
let Some((&author_kind, author)) = context.lookup_pair(&author_id) else {
return Err(QuoteError::AuthorNotFound(author_id));
};
let author = match author_kind {
DestinationKind::Contact
| DestinationKind::Self_
// As of Sep 2024, the release notes channel doesn't currently quote messages,
// but there's no reason it couldn't.
| DestinationKind::ReleaseNotes => {
Ok(author.clone())
}
DestinationKind::Group
| DestinationKind::DistributionList
| DestinationKind::CallLink => Err(QuoteError::InvalidAuthor(author_id, author_kind)),
}?;
let target_sent_timestamp = targetSentTimestamp
.map(|timestamp| Timestamp::from_millis(timestamp, "Quote.targetSentTimestamp"));
@ -142,8 +159,8 @@ mod test {
use test_case::test_case;
use super::*;
use crate::backup::testutil::TestContext;
use crate::backup::recipient::FullRecipientData;
use crate::backup::testutil::TestContext;
use crate::backup::time::testutil::MillisecondsSinceEpoch;
impl proto::quote::QuotedAttachment {
@ -216,4 +233,16 @@ mod test {
}
}
}
#[test_case(|_| {} => Ok(()); "valid")]
#[test_case(|x| x.authorId = 0 => Err(QuoteError::AuthorNotFound(RecipientId(0))); "unknown author")]
#[test_case(|x| {
x.authorId = TestContext::GROUP_ID.0
} => Err(QuoteError::InvalidAuthor(TestContext::GROUP_ID, DestinationKind::Group)); "invalid author")]
#[test_case(|x| x.type_ = proto::quote::Type::UNKNOWN.into() => Err(QuoteError::TypeUnknown); "unknown type")]
fn quote(modifier: impl FnOnce(&mut proto::Quote)) -> Result<(), QuoteError> {
let mut attachment = proto::Quote::test_data();
modifier(&mut attachment);
Quote::try_from_with(attachment, &TestContext::default()).map(|_| ())
}
}

View File

@ -8,7 +8,8 @@ use crate::backup::chat::text::MessageText;
use crate::backup::chat::{ChatItemError, Reaction};
use crate::backup::file::{FilePointer, MessageAttachment};
use crate::backup::frame::RecipientId;
use crate::backup::method::Lookup;
use crate::backup::method::LookupPair;
use crate::backup::recipient::DestinationKind;
use crate::backup::serialize::{SerializeOrder, UnorderedList};
use crate::backup::{TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
@ -27,8 +28,8 @@ pub struct StandardMessage<Recipient> {
_limit_construction_to_module: (),
}
impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::StandardMessage, C>
for StandardMessage<R>
impl<R: Clone, C: LookupPair<RecipientId, DestinationKind, R>>
TryFromWith<proto::StandardMessage, C> for StandardMessage<R>
{
type Error = ChatItemError;
@ -86,8 +87,8 @@ impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::StandardMessage, C>
#[cfg(test)]
mod test {
use super::*;
use crate::backup::testutil::TestContext;
use crate::backup::recipient::FullRecipientData;
use crate::backup::testutil::TestContext;
use crate::backup::time::{Duration, Timestamp};
impl proto::StandardMessage {

View File

@ -4,7 +4,8 @@
use crate::backup::chat::{ChatItemError, Reaction};
use crate::backup::frame::RecipientId;
use crate::backup::method::Lookup;
use crate::backup::method::LookupPair;
use crate::backup::recipient::DestinationKind;
use crate::backup::serialize::{SerializeOrder, UnorderedList};
use crate::backup::sticker::MessageSticker;
use crate::backup::{TryFromWith, TryIntoWith as _};
@ -20,7 +21,7 @@ pub struct StickerMessage<Recipient> {
_limit_construction_to_module: (),
}
impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::StickerMessage, C>
impl<R: Clone, C: LookupPair<RecipientId, DestinationKind, R>> TryFromWith<proto::StickerMessage, C>
for StickerMessage<R>
{
type Error = ChatItemError;
@ -55,9 +56,9 @@ mod test {
use test_case::test_case;
use super::*;
use crate::backup::testutil::TestContext;
use crate::backup::chat::ReactionError;
use crate::backup::recipient::FullRecipientData;
use crate::backup::testutil::TestContext;
impl proto::StickerMessage {
pub(crate) fn test_data() -> Self {

View File

@ -6,8 +6,8 @@ use crate::backup::call::{GroupCall, IndividualCall};
use crate::backup::chat::group::GroupChatUpdate;
use crate::backup::chat::ChatItemError;
use crate::backup::frame::RecipientId;
use crate::backup::method::Lookup;
use crate::backup::recipient::E164;
use crate::backup::method::LookupPair;
use crate::backup::recipient::{DestinationKind, E164};
use crate::backup::time::Duration;
use crate::backup::{TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
@ -49,8 +49,8 @@ pub enum SimpleChatUpdate {
MessageRequestAccepted,
}
impl<C: Lookup<RecipientId, R>, R: Clone> TryFromWith<proto::ChatUpdateMessage, C>
for UpdateMessage<R>
impl<C: LookupPair<RecipientId, DestinationKind, R>, R: Clone>
TryFromWith<proto::ChatUpdateMessage, C> for UpdateMessage<R>
{
type Error = ChatItemError;

View File

@ -6,7 +6,8 @@ use crate::backup::chat::quote::{Quote, QuoteError};
use crate::backup::chat::{Reaction, ReactionError};
use crate::backup::file::{MessageAttachment, MessageAttachmentError};
use crate::backup::frame::RecipientId;
use crate::backup::method::Lookup;
use crate::backup::method::LookupPair;
use crate::backup::recipient::DestinationKind;
use crate::backup::serialize::{SerializeOrder, UnorderedList};
use crate::backup::{TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
@ -39,8 +40,8 @@ pub enum VoiceMessageError {
Reaction(#[from] ReactionError),
}
impl<R: Clone, C: Lookup<RecipientId, R>> TryFromWith<proto::StandardMessage, C>
for VoiceMessage<R>
impl<R: Clone, C: LookupPair<RecipientId, DestinationKind, R>>
TryFromWith<proto::StandardMessage, C> for VoiceMessage<R>
{
type Error = VoiceMessageError;
@ -95,8 +96,8 @@ mod test {
use test_case::test_case;
use super::*;
use crate::backup::testutil::TestContext;
use crate::backup::recipient::FullRecipientData;
use crate::backup::testutil::TestContext;
#[test]
fn valid_voice_message() {

View File

@ -99,6 +99,18 @@ pub enum Destination<M: Method + ReferencedTypes> {
CallLink(M::Value<CallLink>),
}
impl DestinationKind {
pub fn is_individual(&self) -> bool {
match self {
DestinationKind::Contact | DestinationKind::Self_ => true,
DestinationKind::Group
| DestinationKind::DistributionList
| DestinationKind::ReleaseNotes
| DestinationKind::CallLink => false,
}
}
}
impl AsRef<DestinationKind> for DestinationKind {
fn as_ref(&self) -> &DestinationKind {
self
@ -430,17 +442,17 @@ impl<R: Clone, C: LookupPair<RecipientId, DestinationKind, R>>
.into_iter()
.map(|id| {
let id = RecipientId(id);
let (kind, recipient_reference) = context
let (&kind, recipient_reference) = context
.lookup_pair(&id)
.ok_or(RecipientError::DistributionListMemberUnknown(id))?;
match kind {
DestinationKind::Contact => Ok(recipient_reference.clone()),
kind @ (DestinationKind::Group
DestinationKind::Group
| DestinationKind::DistributionList
| DestinationKind::Self_
| DestinationKind::ReleaseNotes
| DestinationKind::CallLink) => {
Err(RecipientError::DistributionListMemberWrongKind(id, *kind))
| DestinationKind::CallLink => {
Err(RecipientError::DistributionListMemberWrongKind(id, kind))
}
}
})

View File

@ -8,6 +8,7 @@ use std::sync::Arc;
use nonzero_ext::nonzero;
use once_cell::sync::Lazy;
use crate::backup::call::CallLink;
use crate::backup::chat::chat_style::{CustomChatColor, CustomColorId};
use crate::backup::chat::PinOrder;
use crate::backup::frame::RecipientId;
@ -40,11 +41,14 @@ static CONTACT_RECIPIENT: Lazy<FullRecipientData> =
Lazy::new(|| FullRecipientData::new(Destination::Contact(ContactData::from_proto_test_data())));
static GROUP_RECIPIENT: Lazy<FullRecipientData> =
Lazy::new(|| FullRecipientData::new(Destination::Group(GroupData::from_proto_test_data())));
static CALL_LINK_RECIPIENT: Lazy<FullRecipientData> =
Lazy::new(|| FullRecipientData::new(Destination::CallLink(CallLink::from_proto_test_data())));
impl TestContext {
pub(super) const CONTACT_ID: RecipientId = RecipientId(123456789);
pub(super) const SELF_ID: RecipientId = RecipientId(1111111111);
pub(super) const GROUP_ID: RecipientId = RecipientId(7000000);
pub(super) const CALL_LINK_ID: RecipientId = RecipientId(0xCA77);
}
impl LookupPair<RecipientId, DestinationKind, FullRecipientData> for TestContext {
@ -56,17 +60,12 @@ impl LookupPair<RecipientId, DestinationKind, FullRecipientData> for TestContext
Self::CONTACT_ID => Some((&DestinationKind::Contact, &CONTACT_RECIPIENT)),
Self::SELF_ID => Some((&DestinationKind::Self_, &SELF_RECIPIENT)),
Self::GROUP_ID => Some((&DestinationKind::Group, &GROUP_RECIPIENT)),
Self::CALL_LINK_ID => Some((&DestinationKind::CallLink, &CALL_LINK_RECIPIENT)),
_ => None,
}
}
}
impl Lookup<RecipientId, FullRecipientData> for TestContext {
fn lookup<'a>(&'a self, key: &'a RecipientId) -> Option<&'a FullRecipientData> {
self.lookup_pair(key).map(|(_kind, data)| data)
}
}
impl Lookup<PinOrder, FullRecipientData> for TestContext {
fn lookup(&self, key: &PinOrder) -> Option<&FullRecipientData> {
(*key == Self::DUPLICATE_PINNED_ORDER).then_some(&SELF_RECIPIENT)