Skip to content

Commit

Permalink
backup: Validate e164s (they must not be 0)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrose-signal committed Jul 31, 2024
1 parent c3b6b85 commit 3e06408
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 13 deletions.
6 changes: 4 additions & 2 deletions rust/message-backup/src/backup/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ pub enum ChatItemError {
RevisionContainsCall,
/// learned profile chat update has no e164 or name
LearnedProfileIsEmpty,
/// invalid e164
InvalidE164,
}

#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -400,8 +402,8 @@ impl<
previous: _,
new: _,
}
| UpdateMessage::ThreadMerge
| UpdateMessage::SessionSwitchover
| UpdateMessage::ThreadMerge { previous_e164: _ }
| UpdateMessage::SessionSwitchover { e164: _ }
| UpdateMessage::LearnedProfileUpdate(_) => (),
},
ChatItemMessage::Standard(_)
Expand Down
33 changes: 23 additions & 10 deletions rust/message-backup/src/backup/chat/update_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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::time::Duration;
use crate::backup::{TryFromWith, TryIntoWith as _};
use crate::proto::backup as proto;
Expand All @@ -19,8 +20,8 @@ pub enum UpdateMessage<Recipient> {
GroupChange { updates: Vec<GroupChatUpdate> },
ExpirationTimerChange { expires_in: Duration },
ProfileChange { previous: String, new: String },
ThreadMerge,
SessionSwitchover,
ThreadMerge { previous_e164: E164 },
SessionSwitchover { e164: E164 },
IndividualCall(IndividualCall),
GroupCall(GroupCall<Recipient>),
LearnedProfileUpdate(proto::learned_profile_chat_update::PreviousName),
Expand Down Expand Up @@ -128,15 +129,21 @@ impl<C: Lookup<RecipientId, R>, R: Clone> TryFromWith<proto::ChatUpdateMessage,
new: newName,
},
Update::ThreadMerge(proto::ThreadMergeChatUpdate {
previousE164,
special_fields: _,
// TODO validate this field
previousE164: _,
}) => UpdateMessage::ThreadMerge,
}) => {
let previous_e164 = previousE164
.try_into()
.map_err(|_| ChatItemError::InvalidE164)?;
UpdateMessage::ThreadMerge { previous_e164 }
}
Update::SessionSwitchover(proto::SessionSwitchoverChatUpdate {
e164,
special_fields: _,
// TODO validate this field
e164: _,
}) => UpdateMessage::SessionSwitchover,
}) => {
let e164 = e164.try_into().map_err(|_| ChatItemError::InvalidE164)?;
UpdateMessage::SessionSwitchover { e164 }
}
Update::IndividualCall(call) => UpdateMessage::IndividualCall(call.try_into()?),
Update::GroupCall(call) => UpdateMessage::GroupCall(call.try_into_with(context)?),
Update::LearnedProfileChange(proto::LearnedProfileChatUpdate {
Expand Down Expand Up @@ -223,8 +230,14 @@ mod test {
#[test_case(proto::SimpleChatUpdate::test_data(), Ok(()))]
#[test_case(proto::ExpirationTimerChatUpdate::default(), Ok(()))]
#[test_case(proto::ProfileChangeChatUpdate::default(), Ok(()))]
#[test_case(proto::ThreadMergeChatUpdate::default(), Ok(()))]
#[test_case(proto::SessionSwitchoverChatUpdate::default(), Ok(()))]
#[test_case(
proto::ThreadMergeChatUpdate::default(),
Err(ChatItemError::InvalidE164)
)]
#[test_case(
proto::SessionSwitchoverChatUpdate::default(),
Err(ChatItemError::InvalidE164)
)]
#[test_case(
proto::LearnedProfileChatUpdate::default(),
Err(ChatItemError::LearnedProfileIsEmpty)
Expand Down
48 changes: 47 additions & 1 deletion rust/message-backup/src/backup/recipient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub enum RecipientError {
MissingDestination,
/// invalid {0}
InvalidServiceId(ServiceIdKind),
/// invalid e164
InvalidE164,
/// profile key is present but invalid
InvalidProfileKey,
/// distribution destination has invalid UUID
Expand Down Expand Up @@ -94,6 +96,37 @@ impl AsRef<DestinationKind> for DestinationKind {
}
}

/// Represents a phone number in E.164 format.
///
/// Due to the changing nature of phone numbers around the world, validation is minimal.
///
/// The ordering should be considered arbitrary; a proper ordering of E164s would use a
/// lexicographic ordering of the decimal digits, but that costs more in CPU. Use the string
/// representation as a sort key if sorting for human consumption.
#[derive(Debug, serde::Serialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(transparent)]
pub struct E164(NonZeroU64);

impl TryFrom<u64> for E164 {
type Error = <NonZeroU64 as TryFrom<u64>>::Error;

fn try_from(value: u64) -> Result<Self, Self::Error> {
Ok(Self(NonZeroU64::try_from(value)?))
}
}

impl From<E164> for u64 {
fn from(value: E164) -> Self {
value.0.into()
}
}

impl std::fmt::Display for E164 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "+{}", self.0)
}
}

#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ContactData {
Expand All @@ -104,7 +137,7 @@ pub struct ContactData {
pub profile_key: Option<ProfileKeyBytes>,
pub username: Option<String>,
pub registration: Registration,
pub e164: Option<u64>,
pub e164: Option<E164>,
pub blocked: bool,
#[serde(serialize_with = "serialize::enum_as_string")]
pub visibility: proto::contact::Visibility,
Expand Down Expand Up @@ -330,6 +363,11 @@ impl TryFrom<proto::Contact> for ContactData {
| proto::contact::Visibility::HIDDEN_MESSAGE_REQUEST) => v,
};

let e164 = e164
.map(E164::try_from)
.transpose()
.map_err(|_| RecipientError::InvalidE164)?;

Ok(Self {
aci,
pni,
Expand Down Expand Up @@ -671,6 +709,12 @@ mod test {
fn visibility_default(input: &mut proto::Contact) {
input.visibility = EnumOrUnknown::default();
}
fn with_e164(input: &mut proto::Contact) {
input.e164 = Some(16505550101);
}
fn with_invalid_e164(input: &mut proto::Contact) {
input.e164 = Some(0);
}

#[test]
fn valid_destination_contact() {
Expand All @@ -696,6 +740,8 @@ mod test {
#[test_case(profile_no_names, Ok(()))]
#[test_case(visibility_hidden, Ok(()))]
#[test_case(visibility_default, Ok(()))]
#[test_case(with_e164, Ok(()))]
#[test_case(with_invalid_e164, Err(RecipientError::InvalidE164))]
fn destination_contact(
modifier: fn(&mut proto::Contact),
expected: Result<(), RecipientError>,
Expand Down

0 comments on commit 3e06408

Please sign in to comment.