1use std::{
16 collections::{BTreeMap, HashMap},
17 ops::Deref,
18 sync::{
19 Arc,
20 atomic::{AtomicBool, Ordering},
21 },
22};
23
24use matrix_sdk_common::locks::RwLock;
25use ruma::{
26 DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, OwnedDeviceId,
27 OwnedDeviceKeyId, UInt, UserId,
28 api::client::keys::upload_signatures::v3::Request as SignatureUploadRequest,
29 events::{AnyToDeviceEventContent, key::verification::VerificationMethod},
30 serde::Raw,
31};
32use serde::{Deserialize, Serialize};
33use serde_json::Value;
34use tracing::{instrument, trace};
35use vodozemac::{Curve25519PublicKey, Ed25519PublicKey, olm::SessionConfig};
36
37use super::{atomic_bool_deserializer, atomic_bool_serializer};
38#[cfg(any(test, feature = "testing", doc))]
39use crate::OlmMachine;
40use crate::{
41 Account, Sas, VerificationRequest,
42 error::{MismatchedIdentityKeysError, OlmError, OlmResult, SignatureError},
43 identities::{OwnUserIdentityData, UserIdentityData},
44 olm::{InboundGroupSession, OutboundGroupSession, Session, ShareInfo, VerifyJson},
45 session_manager::{CollectStrategy, withheld_code_for_device_for_share_strategy},
46 store::{
47 CryptoStoreWrapper, Result as StoreResult,
48 caches::SequenceNumber,
49 types::{Changes, DeviceChanges},
50 },
51 types::{
52 DeviceKey, DeviceKeys, EventEncryptionAlgorithm, Signatures, SignedKey,
53 events::{
54 EventType, forwarded_room_key::ForwardedRoomKeyContent,
55 room::encrypted::ToDeviceEncryptedEventContent,
56 },
57 requests::{OutgoingVerificationRequest, ToDeviceRequest},
58 },
59 verification::VerificationMachine,
60};
61
62pub enum MaybeEncryptedRoomKey {
63 Encrypted {
64 used_session: Box<Session>,
66 share_info: Box<ShareInfo>,
68 message: Raw<AnyToDeviceEventContent>,
69 },
70 MissingSession,
73}
74
75#[derive(Clone, Serialize, Deserialize)]
77pub struct DeviceData {
78 #[serde(alias = "inner")]
79 pub(crate) device_keys: Arc<DeviceKeys>,
80 #[serde(
81 serialize_with = "atomic_bool_serializer",
82 deserialize_with = "atomic_bool_deserializer"
83 )]
84 deleted: Arc<AtomicBool>,
85 trust_state: Arc<RwLock<LocalTrust>>,
86 #[serde(
89 default,
90 serialize_with = "atomic_bool_serializer",
91 deserialize_with = "atomic_bool_deserializer"
92 )]
93 withheld_code_sent: Arc<AtomicBool>,
94 #[serde(default = "default_timestamp")]
97 first_time_seen_ts: MilliSecondsSinceUnixEpoch,
98 #[serde(default)]
101 pub(crate) olm_wedging_index: SequenceNumber,
102}
103
104fn default_timestamp() -> MilliSecondsSinceUnixEpoch {
105 MilliSecondsSinceUnixEpoch(UInt::default())
106}
107
108#[cfg(not(tarpaulin_include))]
109impl std::fmt::Debug for DeviceData {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.debug_struct("DeviceData")
112 .field("user_id", &self.user_id())
113 .field("device_id", &self.device_id())
114 .field("display_name", &self.display_name())
115 .field("keys", self.keys())
116 .field("deleted", &self.deleted.load(Ordering::SeqCst))
117 .field("trust_state", &self.trust_state)
118 .field("withheld_code_sent", &self.withheld_code_sent)
119 .finish()
120 }
121}
122
123#[derive(Clone)]
125pub struct Device {
126 pub(crate) inner: DeviceData,
127 pub(crate) verification_machine: VerificationMachine,
128 pub(crate) own_identity: Option<OwnUserIdentityData>,
129 pub(crate) device_owner_identity: Option<UserIdentityData>,
130}
131
132#[cfg(not(tarpaulin_include))]
133impl std::fmt::Debug for Device {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.debug_struct("Device").field("device", &self.inner).finish()
136 }
137}
138
139impl Deref for Device {
140 type Target = DeviceData;
141
142 fn deref(&self) -> &Self::Target {
143 &self.inner
144 }
145}
146
147impl Device {
148 pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
158 let (sas, request) = self.verification_machine.start_sas(self.inner.clone()).await?;
159
160 if let OutgoingVerificationRequest::ToDevice(r) = request {
161 Ok((sas, r))
162 } else {
163 panic!("Invalid verification request type");
164 }
165 }
166
167 pub fn is_our_own_device(&self) -> bool {
169 let own_ed25519_key = self.verification_machine.store.account.identity_keys.ed25519;
170 let own_curve25519_key = self.verification_machine.store.account.identity_keys.curve25519;
171
172 self.user_id() == self.verification_machine.own_user_id()
173 && self.device_id() == self.verification_machine.own_device_id()
174 && self.ed25519_key().is_some_and(|k| k == own_ed25519_key)
175 && self.curve25519_key().is_some_and(|k| k == own_curve25519_key)
176 }
177
178 pub fn is_owner_of_session(
184 &self,
185 session: &InboundGroupSession,
186 ) -> Result<bool, MismatchedIdentityKeysError> {
187 if session.has_been_imported() {
188 Ok(false)
208 } else if let Some(key) =
209 session.signing_keys().get(&DeviceKeyAlgorithm::Ed25519).and_then(|k| k.ed25519())
210 {
211 let ed25519_comparison = self.ed25519_key().map(|k| k == key);
270 let curve25519_comparison = self.curve25519_key().map(|k| k == session.sender_key());
271
272 match (ed25519_comparison, curve25519_comparison) {
273 (_, Some(false)) | (Some(false), _) => Err(MismatchedIdentityKeysError {
276 key_ed25519: key.into(),
277 device_ed25519: self.ed25519_key().map(Into::into),
278 key_curve25519: session.sender_key().into(),
279 device_curve25519: self.curve25519_key().map(Into::into),
280 }),
281 (Some(true), Some(true)) => Ok(true),
283 _ => Ok(false),
286 }
287 } else {
288 Ok(false)
289 }
290 }
291
292 pub fn is_cross_signed_by_owner(&self) -> bool {
294 self.device_owner_identity
295 .as_ref()
296 .is_some_and(|owner_identity| self.inner.is_cross_signed_by_owner(owner_identity))
297 }
298
299 pub fn is_device_owner_verified(&self) -> bool {
301 self.device_owner_identity.as_ref().is_some_and(|id| match id {
302 UserIdentityData::Own(own_identity) => own_identity.is_verified(),
303 UserIdentityData::Other(other_identity) => {
304 self.own_identity.as_ref().is_some_and(|oi| oi.is_identity_verified(other_identity))
305 }
306 })
307 }
308
309 pub fn request_verification(&self) -> (VerificationRequest, OutgoingVerificationRequest) {
314 self.request_verification_helper(None)
315 }
316
317 pub fn request_verification_with_methods(
326 &self,
327 methods: Vec<VerificationMethod>,
328 ) -> (VerificationRequest, OutgoingVerificationRequest) {
329 self.request_verification_helper(Some(methods))
330 }
331
332 fn request_verification_helper(
333 &self,
334 methods: Option<Vec<VerificationMethod>>,
335 ) -> (VerificationRequest, OutgoingVerificationRequest) {
336 self.verification_machine.request_to_device_verification(
337 self.user_id(),
338 vec![self.device_id().to_owned()],
339 methods,
340 )
341 }
342
343 pub(crate) async fn get_most_recent_session(&self) -> OlmResult<Option<Session>> {
345 self.inner.get_most_recent_session(self.verification_machine.store.inner()).await
346 }
347
348 pub fn is_verified(&self) -> bool {
356 self.inner.is_verified(&self.own_identity, &self.device_owner_identity)
357 }
358
359 pub fn is_cross_signing_trusted(&self) -> bool {
361 self.inner.is_cross_signing_trusted(&self.own_identity, &self.device_owner_identity)
362 }
363
364 pub async fn verify(&self) -> Result<SignatureUploadRequest, SignatureError> {
378 if self.user_id() == self.verification_machine.own_user_id() {
379 Ok(self
380 .verification_machine
381 .store
382 .private_identity
383 .lock()
384 .await
385 .sign_device(&self.inner)
386 .await?)
387 } else {
388 Err(SignatureError::UserIdMismatch)
389 }
390 }
391
392 pub async fn set_local_trust(&self, trust_state: LocalTrust) -> StoreResult<()> {
401 self.inner.set_trust_state(trust_state);
402
403 let changes = Changes {
404 devices: DeviceChanges { changed: vec![self.inner.clone()], ..Default::default() },
405 ..Default::default()
406 };
407
408 self.verification_machine.store.save_changes(changes).await
409 }
410
411 pub(crate) async fn encrypt(
428 &self,
429 event_type: &str,
430 content: impl Serialize,
431 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>, String)> {
432 self.inner.encrypt(self.verification_machine.store.inner(), event_type, content).await
433 }
434
435 pub async fn encrypt_room_key_for_forwarding(
438 &self,
439 session: InboundGroupSession,
440 message_index: Option<u32>,
441 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
442 let content: ForwardedRoomKeyContent = {
443 let export = if let Some(index) = message_index {
444 session.export_at_index(index).await
445 } else {
446 session.export().await
447 };
448
449 export.try_into()?
450 };
451
452 let event_type = content.event_type().to_owned();
453
454 self.encrypt(&event_type, content)
455 .await
456 .map(|(session, message, _message_id)| (session, message))
457 }
458
459 pub async fn encrypt_event_raw(
486 &self,
487 event_type: &str,
488 content: &Value,
489 share_strategy: CollectStrategy,
490 ) -> OlmResult<Raw<ToDeviceEncryptedEventContent>> {
491 if let Some(withheld_code) = withheld_code_for_device_for_share_strategy(
492 &self.inner,
493 share_strategy,
494 &self.own_identity,
495 &self.device_owner_identity,
496 )
497 .await?
498 {
499 return Err(OlmError::Withheld(withheld_code));
500 }
501
502 let (used_session, raw_encrypted, _message_id) = self.encrypt(event_type, content).await?;
503
504 self.verification_machine
506 .store
507 .save_changes(Changes { sessions: vec![used_session], ..Default::default() })
508 .await?;
509
510 Ok(raw_encrypted)
511 }
512
513 pub fn is_dehydrated(&self) -> bool {
515 self.inner.is_dehydrated()
516 }
517}
518
519#[derive(Debug)]
521pub struct UserDevices {
522 pub(crate) inner: HashMap<OwnedDeviceId, DeviceData>,
523 pub(crate) verification_machine: VerificationMachine,
524 pub(crate) own_identity: Option<OwnUserIdentityData>,
525 pub(crate) device_owner_identity: Option<UserIdentityData>,
526}
527
528impl UserDevices {
529 pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
531 self.inner.get(device_id).map(|d| Device {
532 inner: d.clone(),
533 verification_machine: self.verification_machine.clone(),
534 own_identity: self.own_identity.clone(),
535 device_owner_identity: self.device_owner_identity.clone(),
536 })
537 }
538
539 fn own_user_id(&self) -> &UserId {
540 self.verification_machine.own_user_id()
541 }
542
543 fn own_device_id(&self) -> &DeviceId {
544 self.verification_machine.own_device_id()
545 }
546
547 pub fn is_any_verified(&self) -> bool {
553 self.inner
554 .values()
555 .filter(|d| {
556 !(d.user_id() == self.own_user_id() && d.device_id() == self.own_device_id())
557 })
558 .any(|d| d.is_verified(&self.own_identity, &self.device_owner_identity))
559 }
560
561 pub fn keys(&self) -> impl Iterator<Item = &DeviceId> {
563 self.inner.keys().map(Deref::deref)
564 }
565
566 pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
568 self.inner.values().map(move |d| Device {
569 inner: d.clone(),
570 verification_machine: self.verification_machine.clone(),
571 own_identity: self.own_identity.clone(),
572 device_owner_identity: self.device_owner_identity.clone(),
573 })
574 }
575}
576
577#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
579#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
580pub enum LocalTrust {
581 Verified = 0,
583 BlackListed = 1,
585 Ignored = 2,
587 Unset = 3,
589}
590
591impl From<i64> for LocalTrust {
592 fn from(state: i64) -> Self {
593 match state {
594 0 => LocalTrust::Verified,
595 1 => LocalTrust::BlackListed,
596 2 => LocalTrust::Ignored,
597 3 => LocalTrust::Unset,
598 _ => LocalTrust::Unset,
599 }
600 }
601}
602
603impl DeviceData {
604 pub fn new(device_keys: DeviceKeys, trust_state: LocalTrust) -> Self {
608 Self {
609 device_keys: device_keys.into(),
610 trust_state: Arc::new(RwLock::new(trust_state)),
611 deleted: Arc::new(AtomicBool::new(false)),
612 withheld_code_sent: Arc::new(AtomicBool::new(false)),
613 first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
614 olm_wedging_index: Default::default(),
615 }
616 }
617
618 pub fn user_id(&self) -> &UserId {
620 &self.device_keys.user_id
621 }
622
623 pub fn device_id(&self) -> &DeviceId {
625 &self.device_keys.device_id
626 }
627
628 pub fn display_name(&self) -> Option<&str> {
630 self.device_keys.unsigned.device_display_name.as_deref()
631 }
632
633 pub fn get_key(&self, algorithm: DeviceKeyAlgorithm) -> Option<&DeviceKey> {
635 self.device_keys.get_key(algorithm)
636 }
637
638 pub fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
640 self.device_keys.curve25519_key()
641 }
642
643 pub fn ed25519_key(&self) -> Option<Ed25519PublicKey> {
645 self.device_keys.ed25519_key()
646 }
647
648 pub fn keys(&self) -> &BTreeMap<OwnedDeviceKeyId, DeviceKey> {
650 &self.device_keys.keys
651 }
652
653 pub fn signatures(&self) -> &Signatures {
655 &self.device_keys.signatures
656 }
657
658 pub fn local_trust_state(&self) -> LocalTrust {
660 *self.trust_state.read()
661 }
662
663 pub fn is_locally_trusted(&self) -> bool {
665 self.local_trust_state() == LocalTrust::Verified
666 }
667
668 pub fn is_blacklisted(&self) -> bool {
672 self.local_trust_state() == LocalTrust::BlackListed
673 }
674
675 pub(crate) fn set_trust_state(&self, state: LocalTrust) {
680 *self.trust_state.write() = state;
681 }
682
683 pub(crate) fn mark_withheld_code_as_sent(&self) {
684 self.withheld_code_sent.store(true, Ordering::Relaxed)
685 }
686
687 pub fn was_withheld_code_sent(&self) -> bool {
690 self.withheld_code_sent.load(Ordering::Relaxed)
691 }
692
693 pub fn algorithms(&self) -> &[EventEncryptionAlgorithm] {
695 &self.device_keys.algorithms
696 }
697
698 pub fn supports_olm(&self) -> bool {
700 #[cfg(feature = "experimental-algorithms")]
701 {
702 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
703 || self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
704 }
705
706 #[cfg(not(feature = "experimental-algorithms"))]
707 {
708 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
709 }
710 }
711
712 pub(crate) async fn get_most_recent_session(
715 &self,
716 store: &CryptoStoreWrapper,
717 ) -> OlmResult<Option<Session>> {
718 if let Some(sender_key) = self.curve25519_key() {
719 if let Some(sessions) = store.get_sessions(&sender_key.to_base64()).await? {
720 let mut sessions = sessions.lock().await;
721 sessions.sort_by_key(|s| s.creation_time);
722
723 Ok(sessions.last().cloned())
724 } else {
725 Ok(None)
726 }
727 } else {
728 Ok(None)
729 }
730 }
731
732 #[cfg(feature = "experimental-algorithms")]
735 pub fn supports_olm_v2(&self) -> bool {
736 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
737 }
738
739 pub fn olm_session_config(&self) -> SessionConfig {
741 #[cfg(feature = "experimental-algorithms")]
742 if self.supports_olm_v2() {
743 SessionConfig::version_2()
744 } else {
745 SessionConfig::version_1()
746 }
747
748 #[cfg(not(feature = "experimental-algorithms"))]
749 SessionConfig::version_1()
750 }
751
752 pub fn is_deleted(&self) -> bool {
754 self.deleted.load(Ordering::Relaxed)
755 }
756
757 pub(crate) fn is_verified(
758 &self,
759 own_identity: &Option<OwnUserIdentityData>,
760 device_owner: &Option<UserIdentityData>,
761 ) -> bool {
762 self.is_locally_trusted() || self.is_cross_signing_trusted(own_identity, device_owner)
763 }
764
765 pub(crate) fn is_cross_signing_trusted(
766 &self,
767 own_identity: &Option<OwnUserIdentityData>,
768 device_owner: &Option<UserIdentityData>,
769 ) -> bool {
770 own_identity.as_ref().zip(device_owner.as_ref()).is_some_and(
771 |(own_identity, device_identity)| {
772 match device_identity {
773 UserIdentityData::Own(_) => {
774 own_identity.is_verified() && own_identity.is_device_signed(self)
775 }
776
777 UserIdentityData::Other(device_identity) => {
781 own_identity.is_identity_verified(device_identity)
782 && device_identity.is_device_signed(self)
783 }
784 }
785 },
786 )
787 }
788
789 pub(crate) fn is_cross_signed_by_owner(
790 &self,
791 device_owner_identity: &UserIdentityData,
792 ) -> bool {
793 match device_owner_identity {
794 UserIdentityData::Own(identity) => identity.is_device_signed(self),
797 UserIdentityData::Other(device_identity) => device_identity.is_device_signed(self),
800 }
801 }
802
803 #[instrument(
822 skip_all,
823 fields(
824 recipient = ?self.user_id(),
825 recipient_device = ?self.device_id(),
826 recipient_key = ?self.curve25519_key(),
827 event_type,
828 message_id,
829 ))
830 ]
831 pub(crate) async fn encrypt(
832 &self,
833 store: &CryptoStoreWrapper,
834 event_type: &str,
835 content: impl Serialize,
836 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>, String)> {
837 #[cfg(not(target_family = "wasm"))]
838 let message_id = ulid::Ulid::new().to_string();
839 #[cfg(target_family = "wasm")]
840 let message_id = ruma::TransactionId::new().to_string();
841
842 tracing::Span::current().record("message_id", &message_id);
843
844 let session = self.get_most_recent_session(store).await?;
845
846 if let Some(mut session) = session {
847 let message =
848 session.encrypt(self, event_type, content, Some(message_id.clone())).await?;
849
850 Ok((session, message, message_id))
851 } else {
852 trace!("Trying to encrypt an event for a device, but no Olm session is found.");
853 Err(OlmError::MissingSession)
854 }
855 }
856
857 pub(crate) async fn maybe_encrypt_room_key(
858 &self,
859 store: &CryptoStoreWrapper,
860 session: OutboundGroupSession,
861 ) -> OlmResult<MaybeEncryptedRoomKey> {
862 let content = session.as_content().await;
863 let message_index = session.message_index().await;
864 let event_type = content.event_type().to_owned();
865
866 match self.encrypt(store, &event_type, content).await {
867 Ok((session, encrypted, _)) => Ok(MaybeEncryptedRoomKey::Encrypted {
868 share_info: Box::new(ShareInfo::new_shared(
869 session.sender_key().to_owned(),
870 message_index,
871 self.olm_wedging_index,
872 )),
873 used_session: Box::new(session),
874 message: encrypted.cast(),
875 }),
876
877 Err(OlmError::MissingSession) => Ok(MaybeEncryptedRoomKey::MissingSession),
878 Err(e) => Err(e),
879 }
880 }
881
882 pub(crate) fn update_device(
886 &mut self,
887 device_keys: &DeviceKeys,
888 ) -> Result<bool, SignatureError> {
889 device_keys.check_self_signature()?;
890
891 if self.user_id() != device_keys.user_id || self.device_id() != device_keys.device_id {
892 Err(SignatureError::UserIdMismatch)
893 } else if self.ed25519_key() != device_keys.ed25519_key() {
894 Err(SignatureError::SigningKeyChanged(
895 self.ed25519_key().map(Box::new),
896 device_keys.ed25519_key().map(Box::new),
897 ))
898 } else if self.device_keys.as_ref() != device_keys {
899 trace!(
900 user_id = ?self.user_id(),
901 device_id = ?self.device_id(),
902 keys = ?self.keys(),
903 "Updated a device",
904 );
905
906 self.device_keys = device_keys.clone().into();
907
908 Ok(true)
909 } else {
910 Ok(false)
912 }
913 }
914
915 pub fn as_device_keys(&self) -> &DeviceKeys {
917 &self.device_keys
918 }
919
920 pub(crate) fn has_signed_raw(
930 &self,
931 signatures: &Signatures,
932 canonical_json: &str,
933 ) -> Result<(), SignatureError> {
934 let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
935 let user_id = self.user_id();
936 let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id());
937
938 key.verify_canonicalized_json(user_id, key_id, signatures, canonical_json)
939 }
940
941 pub(crate) fn verify_one_time_key(
942 &self,
943 one_time_key: &SignedKey,
944 ) -> Result<(), SignatureError> {
945 self.device_keys.has_signed(one_time_key)
946 }
947
948 pub(crate) fn mark_as_deleted(&self) {
950 self.deleted.store(true, Ordering::Relaxed);
951 }
952
953 #[cfg(any(test, feature = "testing"))]
954 #[allow(dead_code)]
955 pub async fn from_machine_test_helper(
957 machine: &OlmMachine,
958 ) -> Result<DeviceData, crate::CryptoStoreError> {
959 Ok(DeviceData::from_account(&*machine.store().cache().await?.account().await?))
960 }
961
962 pub fn from_account(account: &Account) -> DeviceData {
975 let device_keys = account.device_keys();
976 let mut device = DeviceData::try_from(&device_keys)
977 .expect("Creating a device from our own account should always succeed");
978 device.first_time_seen_ts = account.creation_local_time();
979
980 device
981 }
982
983 pub fn first_time_seen_ts(&self) -> MilliSecondsSinceUnixEpoch {
986 self.first_time_seen_ts
987 }
988
989 pub fn is_dehydrated(&self) -> bool {
991 self.device_keys.dehydrated.unwrap_or(false)
992 }
993}
994
995impl TryFrom<&DeviceKeys> for DeviceData {
996 type Error = SignatureError;
997
998 fn try_from(device_keys: &DeviceKeys) -> Result<Self, Self::Error> {
999 device_keys.check_self_signature()?;
1000 Ok(Self {
1001 device_keys: device_keys.clone().into(),
1002 deleted: Arc::new(AtomicBool::new(false)),
1003 trust_state: Arc::new(RwLock::new(LocalTrust::Unset)),
1004 withheld_code_sent: Arc::new(AtomicBool::new(false)),
1005 first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
1006 olm_wedging_index: Default::default(),
1007 })
1008 }
1009}
1010
1011impl PartialEq for DeviceData {
1012 fn eq(&self, other: &Self) -> bool {
1013 self.user_id() == other.user_id() && self.device_id() == other.device_id()
1014 }
1015}
1016
1017#[cfg(any(test, feature = "testing"))]
1019#[allow(dead_code)]
1020pub(crate) mod testing {
1021 use serde_json::json;
1022
1023 use crate::{identities::DeviceData, types::DeviceKeys};
1024
1025 pub fn device_keys() -> DeviceKeys {
1027 let device_keys = json!({
1028 "algorithms": vec![
1029 "m.olm.v1.curve25519-aes-sha2",
1030 "m.megolm.v1.aes-sha2"
1031 ],
1032 "device_id": "BNYQQWUMXO",
1033 "user_id": "@example:localhost",
1034 "keys": {
1035 "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1036 "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1037 },
1038 "signatures": {
1039 "@example:localhost": {
1040 "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1041 }
1042 },
1043 "unsigned": {
1044 "device_display_name": "Alice's mobile phone"
1045 }
1046 });
1047
1048 serde_json::from_value(device_keys).unwrap()
1049 }
1050
1051 pub fn get_device() -> DeviceData {
1053 let device_keys = device_keys();
1054 DeviceData::try_from(&device_keys).unwrap()
1055 }
1056}
1057
1058#[cfg(test)]
1059pub(crate) mod tests {
1060 use ruma::{MilliSecondsSinceUnixEpoch, user_id};
1061 use serde_json::json;
1062 use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
1063
1064 use super::testing::{device_keys, get_device};
1065 use crate::{DeviceData, identities::LocalTrust};
1066
1067 #[test]
1068 fn create_a_device() {
1069 let now = MilliSecondsSinceUnixEpoch::now();
1070 let user_id = user_id!("@example:localhost");
1071 let device_id = "BNYQQWUMXO";
1072
1073 let device = get_device();
1074
1075 assert_eq!(user_id, device.user_id());
1076 assert_eq!(device_id, device.device_id());
1077 assert_eq!(device.algorithms().len(), 2);
1078 assert_eq!(LocalTrust::Unset, device.local_trust_state());
1079 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1080 assert_eq!(
1081 device.curve25519_key().unwrap(),
1082 Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1083 .unwrap(),
1084 );
1085 assert_eq!(
1086 device.ed25519_key().unwrap(),
1087 Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1088 );
1089
1090 let then = MilliSecondsSinceUnixEpoch::now();
1091
1092 assert!(device.first_time_seen_ts() >= now);
1093 assert!(device.first_time_seen_ts() <= then);
1094 }
1095
1096 #[test]
1097 fn update_a_device() {
1098 let mut device = get_device();
1099
1100 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1101
1102 let display_name = "Alice's work computer".to_owned();
1103
1104 let mut device_keys = device_keys();
1105 device_keys.unsigned.device_display_name = Some(display_name.clone());
1106 assert!(device.update_device(&device_keys).unwrap());
1107 assert_eq!(&display_name, device.display_name().as_ref().unwrap());
1108
1109 assert!(!device.update_device(&device_keys).unwrap());
1111 }
1112
1113 #[test]
1114 #[allow(clippy::redundant_clone)]
1115 fn delete_a_device() {
1116 let device = get_device();
1117 assert!(!device.is_deleted());
1118
1119 let device_clone = device.clone();
1120
1121 device.mark_as_deleted();
1122 assert!(device.is_deleted());
1123 assert!(device_clone.is_deleted());
1124 }
1125
1126 #[test]
1127 fn deserialize_device() {
1128 let user_id = user_id!("@example:localhost");
1129 let device_id = "BNYQQWUMXO";
1130
1131 let device = json!({
1132 "inner": {
1133 "user_id": user_id,
1134 "device_id": device_id,
1135 "algorithms": ["m.olm.v1.curve25519-aes-sha2","m.megolm.v1.aes-sha2"],
1136 "keys": {
1137 "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1138 "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1139 },
1140 "signatures": {
1141 "@example:localhost": {
1142 "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1143 }
1144 },
1145 "unsigned": {
1146 "device_display_name": "Alice's mobile phone"
1147 }
1148 },
1149 "deleted": false,
1150 "trust_state": "Verified",
1151 "withheld_code_sent": false,
1152 "first_time_seen_ts": 1696931068314u64
1153 });
1154
1155 let device: DeviceData =
1156 serde_json::from_value(device).expect("We should be able to deserialize our device");
1157
1158 assert_eq!(user_id, device.user_id());
1159 assert_eq!(device_id, device.device_id());
1160 assert_eq!(device.algorithms().len(), 2);
1161 assert_eq!(LocalTrust::Verified, device.local_trust_state());
1162 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1163 assert_eq!(
1164 device.curve25519_key().unwrap(),
1165 Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1166 .unwrap(),
1167 );
1168 assert_eq!(
1169 device.ed25519_key().unwrap(),
1170 Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1171 );
1172 }
1173}