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:
parent
358164c537
commit
6529c3d189
@ -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())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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(|_| ())
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user