1use std::{
16 collections::{BTreeMap, HashMap},
17 ops::Deref,
18 sync::{
19 atomic::{AtomicBool, Ordering},
20 Arc,
21 },
22};
23
24use matrix_sdk_common::locks::RwLock;
25use ruma::{
26 api::client::keys::upload_signatures::v3::Request as SignatureUploadRequest,
27 events::{key::verification::VerificationMethod, AnyToDeviceEventContent},
28 serde::Raw,
29 DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, OwnedDeviceId,
30 OwnedDeviceKeyId, UInt, UserId,
31};
32use serde::{Deserialize, Serialize};
33use serde_json::Value;
34use tracing::{instrument, trace};
35use vodozemac::{olm::SessionConfig, Curve25519PublicKey, Ed25519PublicKey};
36
37use super::{atomic_bool_deserializer, atomic_bool_serializer};
38#[cfg(any(test, feature = "testing", doc))]
39use crate::OlmMachine;
40use crate::{
41 error::{MismatchedIdentityKeysError, OlmError, OlmResult, SignatureError},
42 identities::{OwnUserIdentityData, UserIdentityData},
43 olm::{
44 InboundGroupSession, OutboundGroupSession, Session, ShareInfo, SignedJsonObject, VerifyJson,
45 },
46 store::{
47 caches::SequenceNumber, Changes, CryptoStoreWrapper, DeviceChanges, Result as StoreResult,
48 },
49 types::{
50 events::{
51 forwarded_room_key::ForwardedRoomKeyContent,
52 room::encrypted::ToDeviceEncryptedEventContent, EventType,
53 },
54 requests::{OutgoingVerificationRequest, ToDeviceRequest},
55 DeviceKey, DeviceKeys, EventEncryptionAlgorithm, Signatures, SignedKey,
56 },
57 verification::VerificationMachine,
58 Account, Sas, VerificationRequest,
59};
60
61pub enum MaybeEncryptedRoomKey {
62 Encrypted {
63 used_session: Box<Session>,
65 share_info: Box<ShareInfo>,
67 message: Raw<AnyToDeviceEventContent>,
68 },
69 MissingSession,
72}
73
74#[derive(Clone, Serialize, Deserialize)]
76pub struct DeviceData {
77 #[serde(alias = "inner")]
78 pub(crate) device_keys: Arc<DeviceKeys>,
79 #[serde(
80 serialize_with = "atomic_bool_serializer",
81 deserialize_with = "atomic_bool_deserializer"
82 )]
83 deleted: Arc<AtomicBool>,
84 trust_state: Arc<RwLock<LocalTrust>>,
85 #[serde(
88 default,
89 serialize_with = "atomic_bool_serializer",
90 deserialize_with = "atomic_bool_deserializer"
91 )]
92 withheld_code_sent: Arc<AtomicBool>,
93 #[serde(default = "default_timestamp")]
96 first_time_seen_ts: MilliSecondsSinceUnixEpoch,
97 #[serde(default)]
100 pub(crate) olm_wedging_index: SequenceNumber,
101}
102
103fn default_timestamp() -> MilliSecondsSinceUnixEpoch {
104 MilliSecondsSinceUnixEpoch(UInt::default())
105}
106
107#[cfg(not(tarpaulin_include))]
108impl std::fmt::Debug for DeviceData {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.debug_struct("DeviceData")
111 .field("user_id", &self.user_id())
112 .field("device_id", &self.device_id())
113 .field("display_name", &self.display_name())
114 .field("keys", self.keys())
115 .field("deleted", &self.deleted.load(Ordering::SeqCst))
116 .field("trust_state", &self.trust_state)
117 .field("withheld_code_sent", &self.withheld_code_sent)
118 .finish()
119 }
120}
121
122#[derive(Clone)]
124pub struct Device {
125 pub(crate) inner: DeviceData,
126 pub(crate) verification_machine: VerificationMachine,
127 pub(crate) own_identity: Option<OwnUserIdentityData>,
128 pub(crate) device_owner_identity: Option<UserIdentityData>,
129}
130
131#[cfg(not(tarpaulin_include))]
132impl std::fmt::Debug for Device {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 f.debug_struct("Device").field("device", &self.inner).finish()
135 }
136}
137
138impl Deref for Device {
139 type Target = DeviceData;
140
141 fn deref(&self) -> &Self::Target {
142 &self.inner
143 }
144}
145
146impl Device {
147 pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
157 let (sas, request) = self.verification_machine.start_sas(self.inner.clone()).await?;
158
159 if let OutgoingVerificationRequest::ToDevice(r) = request {
160 Ok((sas, r))
161 } else {
162 panic!("Invalid verification request type");
163 }
164 }
165
166 pub fn is_our_own_device(&self) -> bool {
168 let own_ed25519_key = self.verification_machine.store.account.identity_keys.ed25519;
169 let own_curve25519_key = self.verification_machine.store.account.identity_keys.curve25519;
170
171 self.user_id() == self.verification_machine.own_user_id()
172 && self.device_id() == self.verification_machine.own_device_id()
173 && self.ed25519_key().is_some_and(|k| k == own_ed25519_key)
174 && self.curve25519_key().is_some_and(|k| k == own_curve25519_key)
175 }
176
177 pub fn is_owner_of_session(
183 &self,
184 session: &InboundGroupSession,
185 ) -> Result<bool, MismatchedIdentityKeysError> {
186 if session.has_been_imported() {
187 Ok(false)
207 } else if let Some(key) =
208 session.signing_keys().get(&DeviceKeyAlgorithm::Ed25519).and_then(|k| k.ed25519())
209 {
210 let ed25519_comparison = self.ed25519_key().map(|k| k == key);
269 let curve25519_comparison = self.curve25519_key().map(|k| k == session.sender_key());
270
271 match (ed25519_comparison, curve25519_comparison) {
272 (_, Some(false)) | (Some(false), _) => Err(MismatchedIdentityKeysError {
275 key_ed25519: key.into(),
276 device_ed25519: self.ed25519_key().map(Into::into),
277 key_curve25519: session.sender_key().into(),
278 device_curve25519: self.curve25519_key().map(Into::into),
279 }),
280 (Some(true), Some(true)) => Ok(true),
282 _ => Ok(false),
285 }
286 } else {
287 Ok(false)
288 }
289 }
290
291 pub fn is_cross_signed_by_owner(&self) -> bool {
293 self.device_owner_identity
294 .as_ref()
295 .is_some_and(|owner_identity| self.inner.is_cross_signed_by_owner(owner_identity))
296 }
297
298 pub fn is_device_owner_verified(&self) -> bool {
300 self.device_owner_identity.as_ref().is_some_and(|id| match id {
301 UserIdentityData::Own(own_identity) => own_identity.is_verified(),
302 UserIdentityData::Other(other_identity) => {
303 self.own_identity.as_ref().is_some_and(|oi| oi.is_identity_verified(other_identity))
304 }
305 })
306 }
307
308 pub fn request_verification(&self) -> (VerificationRequest, OutgoingVerificationRequest) {
313 self.request_verification_helper(None)
314 }
315
316 pub fn request_verification_with_methods(
325 &self,
326 methods: Vec<VerificationMethod>,
327 ) -> (VerificationRequest, OutgoingVerificationRequest) {
328 self.request_verification_helper(Some(methods))
329 }
330
331 fn request_verification_helper(
332 &self,
333 methods: Option<Vec<VerificationMethod>>,
334 ) -> (VerificationRequest, OutgoingVerificationRequest) {
335 self.verification_machine.request_to_device_verification(
336 self.user_id(),
337 vec![self.device_id().to_owned()],
338 methods,
339 )
340 }
341
342 pub(crate) async fn get_most_recent_session(&self) -> OlmResult<Option<Session>> {
344 self.inner.get_most_recent_session(self.verification_machine.store.inner()).await
345 }
346
347 pub fn is_verified(&self) -> bool {
355 self.inner.is_verified(&self.own_identity, &self.device_owner_identity)
356 }
357
358 pub fn is_cross_signing_trusted(&self) -> bool {
360 self.inner.is_cross_signing_trusted(&self.own_identity, &self.device_owner_identity)
361 }
362
363 pub async fn verify(&self) -> Result<SignatureUploadRequest, SignatureError> {
377 if self.user_id() == self.verification_machine.own_user_id() {
378 Ok(self
379 .verification_machine
380 .store
381 .private_identity
382 .lock()
383 .await
384 .sign_device(&self.inner)
385 .await?)
386 } else {
387 Err(SignatureError::UserIdMismatch)
388 }
389 }
390
391 pub async fn set_local_trust(&self, trust_state: LocalTrust) -> StoreResult<()> {
400 self.inner.set_trust_state(trust_state);
401
402 let changes = Changes {
403 devices: DeviceChanges { changed: vec![self.inner.clone()], ..Default::default() },
404 ..Default::default()
405 };
406
407 self.verification_machine.store.save_changes(changes).await
408 }
409
410 pub(crate) async fn encrypt(
416 &self,
417 event_type: &str,
418 content: impl Serialize,
419 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
420 self.inner.encrypt(self.verification_machine.store.inner(), event_type, content).await
421 }
422
423 pub async fn encrypt_room_key_for_forwarding(
426 &self,
427 session: InboundGroupSession,
428 message_index: Option<u32>,
429 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
430 let content: ForwardedRoomKeyContent = {
431 let export = if let Some(index) = message_index {
432 session.export_at_index(index).await
433 } else {
434 session.export().await
435 };
436
437 export.try_into()?
438 };
439
440 let event_type = content.event_type().to_owned();
441
442 self.encrypt(&event_type, content).await
443 }
444
445 pub async fn encrypt_event_raw(
470 &self,
471 event_type: &str,
472 content: &Value,
473 ) -> OlmResult<Raw<ToDeviceEncryptedEventContent>> {
474 let (used_session, raw_encrypted) = self.encrypt(event_type, content).await?;
475
476 self.verification_machine
478 .store
479 .save_changes(Changes { sessions: vec![used_session], ..Default::default() })
480 .await?;
481
482 Ok(raw_encrypted)
483 }
484
485 pub fn is_dehydrated(&self) -> bool {
487 self.inner.is_dehydrated()
488 }
489}
490
491#[derive(Debug)]
493pub struct UserDevices {
494 pub(crate) inner: HashMap<OwnedDeviceId, DeviceData>,
495 pub(crate) verification_machine: VerificationMachine,
496 pub(crate) own_identity: Option<OwnUserIdentityData>,
497 pub(crate) device_owner_identity: Option<UserIdentityData>,
498}
499
500impl UserDevices {
501 pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
503 self.inner.get(device_id).map(|d| Device {
504 inner: d.clone(),
505 verification_machine: self.verification_machine.clone(),
506 own_identity: self.own_identity.clone(),
507 device_owner_identity: self.device_owner_identity.clone(),
508 })
509 }
510
511 fn own_user_id(&self) -> &UserId {
512 self.verification_machine.own_user_id()
513 }
514
515 fn own_device_id(&self) -> &DeviceId {
516 self.verification_machine.own_device_id()
517 }
518
519 pub fn is_any_verified(&self) -> bool {
525 self.inner
526 .values()
527 .filter(|d| {
528 !(d.user_id() == self.own_user_id() && d.device_id() == self.own_device_id())
529 })
530 .any(|d| d.is_verified(&self.own_identity, &self.device_owner_identity))
531 }
532
533 pub fn keys(&self) -> impl Iterator<Item = &DeviceId> {
535 self.inner.keys().map(Deref::deref)
536 }
537
538 pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
540 self.inner.values().map(move |d| Device {
541 inner: d.clone(),
542 verification_machine: self.verification_machine.clone(),
543 own_identity: self.own_identity.clone(),
544 device_owner_identity: self.device_owner_identity.clone(),
545 })
546 }
547}
548
549#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
551#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
552pub enum LocalTrust {
553 Verified = 0,
555 BlackListed = 1,
557 Ignored = 2,
559 Unset = 3,
561}
562
563impl From<i64> for LocalTrust {
564 fn from(state: i64) -> Self {
565 match state {
566 0 => LocalTrust::Verified,
567 1 => LocalTrust::BlackListed,
568 2 => LocalTrust::Ignored,
569 3 => LocalTrust::Unset,
570 _ => LocalTrust::Unset,
571 }
572 }
573}
574
575impl DeviceData {
576 pub fn new(device_keys: DeviceKeys, trust_state: LocalTrust) -> Self {
580 Self {
581 device_keys: device_keys.into(),
582 trust_state: Arc::new(RwLock::new(trust_state)),
583 deleted: Arc::new(AtomicBool::new(false)),
584 withheld_code_sent: Arc::new(AtomicBool::new(false)),
585 first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
586 olm_wedging_index: Default::default(),
587 }
588 }
589
590 pub fn user_id(&self) -> &UserId {
592 &self.device_keys.user_id
593 }
594
595 pub fn device_id(&self) -> &DeviceId {
597 &self.device_keys.device_id
598 }
599
600 pub fn display_name(&self) -> Option<&str> {
602 self.device_keys.unsigned.device_display_name.as_deref()
603 }
604
605 pub fn get_key(&self, algorithm: DeviceKeyAlgorithm) -> Option<&DeviceKey> {
607 self.device_keys.get_key(algorithm)
608 }
609
610 pub fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
612 self.device_keys.curve25519_key()
613 }
614
615 pub fn ed25519_key(&self) -> Option<Ed25519PublicKey> {
617 self.device_keys.ed25519_key()
618 }
619
620 pub fn keys(&self) -> &BTreeMap<OwnedDeviceKeyId, DeviceKey> {
622 &self.device_keys.keys
623 }
624
625 pub fn signatures(&self) -> &Signatures {
627 &self.device_keys.signatures
628 }
629
630 pub fn local_trust_state(&self) -> LocalTrust {
632 *self.trust_state.read()
633 }
634
635 pub fn is_locally_trusted(&self) -> bool {
637 self.local_trust_state() == LocalTrust::Verified
638 }
639
640 pub fn is_blacklisted(&self) -> bool {
644 self.local_trust_state() == LocalTrust::BlackListed
645 }
646
647 pub(crate) fn set_trust_state(&self, state: LocalTrust) {
652 *self.trust_state.write() = state;
653 }
654
655 pub(crate) fn mark_withheld_code_as_sent(&self) {
656 self.withheld_code_sent.store(true, Ordering::Relaxed)
657 }
658
659 pub fn was_withheld_code_sent(&self) -> bool {
662 self.withheld_code_sent.load(Ordering::Relaxed)
663 }
664
665 pub fn algorithms(&self) -> &[EventEncryptionAlgorithm] {
667 &self.device_keys.algorithms
668 }
669
670 pub fn supports_olm(&self) -> bool {
672 #[cfg(feature = "experimental-algorithms")]
673 {
674 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
675 || self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
676 }
677
678 #[cfg(not(feature = "experimental-algorithms"))]
679 {
680 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
681 }
682 }
683
684 pub(crate) async fn get_most_recent_session(
687 &self,
688 store: &CryptoStoreWrapper,
689 ) -> OlmResult<Option<Session>> {
690 if let Some(sender_key) = self.curve25519_key() {
691 if let Some(sessions) = store.get_sessions(&sender_key.to_base64()).await? {
692 let mut sessions = sessions.lock().await;
693 sessions.sort_by_key(|s| s.creation_time);
694
695 Ok(sessions.last().cloned())
696 } else {
697 Ok(None)
698 }
699 } else {
700 Ok(None)
701 }
702 }
703
704 #[cfg(feature = "experimental-algorithms")]
707 pub fn supports_olm_v2(&self) -> bool {
708 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
709 }
710
711 pub fn olm_session_config(&self) -> SessionConfig {
713 #[cfg(feature = "experimental-algorithms")]
714 if self.supports_olm_v2() {
715 SessionConfig::version_2()
716 } else {
717 SessionConfig::version_1()
718 }
719
720 #[cfg(not(feature = "experimental-algorithms"))]
721 SessionConfig::version_1()
722 }
723
724 pub fn is_deleted(&self) -> bool {
726 self.deleted.load(Ordering::Relaxed)
727 }
728
729 pub(crate) fn is_verified(
730 &self,
731 own_identity: &Option<OwnUserIdentityData>,
732 device_owner: &Option<UserIdentityData>,
733 ) -> bool {
734 self.is_locally_trusted() || self.is_cross_signing_trusted(own_identity, device_owner)
735 }
736
737 pub(crate) fn is_cross_signing_trusted(
738 &self,
739 own_identity: &Option<OwnUserIdentityData>,
740 device_owner: &Option<UserIdentityData>,
741 ) -> bool {
742 own_identity.as_ref().zip(device_owner.as_ref()).is_some_and(
743 |(own_identity, device_identity)| {
744 match device_identity {
745 UserIdentityData::Own(_) => {
746 own_identity.is_verified() && own_identity.is_device_signed(self)
747 }
748
749 UserIdentityData::Other(device_identity) => {
753 own_identity.is_identity_verified(device_identity)
754 && device_identity.is_device_signed(self)
755 }
756 }
757 },
758 )
759 }
760
761 pub(crate) fn is_cross_signed_by_owner(
762 &self,
763 device_owner_identity: &UserIdentityData,
764 ) -> bool {
765 match device_owner_identity {
766 UserIdentityData::Own(identity) => identity.is_device_signed(self),
769 UserIdentityData::Other(device_identity) => device_identity.is_device_signed(self),
772 }
773 }
774
775 #[instrument(
793 skip_all,
794 fields(
795 recipient = ?self.user_id(),
796 recipient_device = ?self.device_id(),
797 recipient_key = ?self.curve25519_key(),
798 event_type,
799 message_id,
800 ))
801 ]
802 pub(crate) async fn encrypt(
803 &self,
804 store: &CryptoStoreWrapper,
805 event_type: &str,
806 content: impl Serialize,
807 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
808 #[cfg(not(target_arch = "wasm32"))]
809 let message_id = ulid::Ulid::new().to_string();
810 #[cfg(target_arch = "wasm32")]
811 let message_id = ruma::TransactionId::new().to_string();
812
813 tracing::Span::current().record("message_id", &message_id);
814
815 let session = self.get_most_recent_session(store).await?;
816
817 if let Some(mut session) = session {
818 let message = session.encrypt(self, event_type, content, Some(message_id)).await?;
819 Ok((session, message))
820 } else {
821 trace!("Trying to encrypt an event for a device, but no Olm session is found.");
822 Err(OlmError::MissingSession)
823 }
824 }
825
826 pub(crate) async fn maybe_encrypt_room_key(
827 &self,
828 store: &CryptoStoreWrapper,
829 session: OutboundGroupSession,
830 ) -> OlmResult<MaybeEncryptedRoomKey> {
831 let content = session.as_content().await;
832 let message_index = session.message_index().await;
833 let event_type = content.event_type().to_owned();
834
835 match self.encrypt(store, &event_type, content).await {
836 Ok((session, encrypted)) => Ok(MaybeEncryptedRoomKey::Encrypted {
837 share_info: Box::new(ShareInfo::new_shared(
838 session.sender_key().to_owned(),
839 message_index,
840 self.olm_wedging_index,
841 )),
842 used_session: Box::new(session),
843 message: encrypted.cast(),
844 }),
845
846 Err(OlmError::MissingSession) => Ok(MaybeEncryptedRoomKey::MissingSession),
847 Err(e) => Err(e),
848 }
849 }
850
851 pub(crate) fn update_device(
855 &mut self,
856 device_keys: &DeviceKeys,
857 ) -> Result<bool, SignatureError> {
858 self.verify_device_keys(device_keys)?;
859
860 if self.user_id() != device_keys.user_id || self.device_id() != device_keys.device_id {
861 Err(SignatureError::UserIdMismatch)
862 } else if self.ed25519_key() != device_keys.ed25519_key() {
863 Err(SignatureError::SigningKeyChanged(
864 self.ed25519_key().map(Box::new),
865 device_keys.ed25519_key().map(Box::new),
866 ))
867 } else if self.device_keys.as_ref() != device_keys {
868 trace!(
869 user_id = ?self.user_id(),
870 device_id = ?self.device_id(),
871 keys = ?self.keys(),
872 "Updated a device",
873 );
874
875 self.device_keys = device_keys.clone().into();
876
877 Ok(true)
878 } else {
879 Ok(false)
881 }
882 }
883
884 pub fn as_device_keys(&self) -> &DeviceKeys {
886 &self.device_keys
887 }
888
889 pub(crate) fn has_signed_raw(
899 &self,
900 signatures: &Signatures,
901 canonical_json: &str,
902 ) -> Result<(), SignatureError> {
903 let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
904 let user_id = self.user_id();
905 let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id());
906
907 key.verify_canonicalized_json(user_id, key_id, signatures, canonical_json)
908 }
909
910 fn has_signed(&self, signed_object: &impl SignedJsonObject) -> Result<(), SignatureError> {
911 let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
912 let user_id = self.user_id();
913 let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id());
914
915 key.verify_json(user_id, key_id, signed_object)
916 }
917
918 pub(crate) fn verify_device_keys(
919 &self,
920 device_keys: &DeviceKeys,
921 ) -> Result<(), SignatureError> {
922 self.has_signed(device_keys)
923 }
924
925 pub(crate) fn verify_one_time_key(
926 &self,
927 one_time_key: &SignedKey,
928 ) -> Result<(), SignatureError> {
929 self.has_signed(one_time_key)
930 }
931
932 pub(crate) fn mark_as_deleted(&self) {
934 self.deleted.store(true, Ordering::Relaxed);
935 }
936
937 #[cfg(any(test, feature = "testing"))]
938 #[allow(dead_code)]
939 pub async fn from_machine_test_helper(
941 machine: &OlmMachine,
942 ) -> Result<DeviceData, crate::CryptoStoreError> {
943 Ok(DeviceData::from_account(&*machine.store().cache().await?.account().await?))
944 }
945
946 pub fn from_account(account: &Account) -> DeviceData {
959 let device_keys = account.device_keys();
960 let mut device = DeviceData::try_from(&device_keys)
961 .expect("Creating a device from our own account should always succeed");
962 device.first_time_seen_ts = account.creation_local_time();
963
964 device
965 }
966
967 pub fn first_time_seen_ts(&self) -> MilliSecondsSinceUnixEpoch {
970 self.first_time_seen_ts
971 }
972
973 pub fn is_dehydrated(&self) -> bool {
975 self.device_keys.dehydrated.unwrap_or(false)
976 }
977}
978
979impl TryFrom<&DeviceKeys> for DeviceData {
980 type Error = SignatureError;
981
982 fn try_from(device_keys: &DeviceKeys) -> Result<Self, Self::Error> {
983 let device = Self {
984 device_keys: device_keys.clone().into(),
985 deleted: Arc::new(AtomicBool::new(false)),
986 trust_state: Arc::new(RwLock::new(LocalTrust::Unset)),
987 withheld_code_sent: Arc::new(AtomicBool::new(false)),
988 first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
989 olm_wedging_index: Default::default(),
990 };
991
992 device.verify_device_keys(device_keys)?;
993 Ok(device)
994 }
995}
996
997impl PartialEq for DeviceData {
998 fn eq(&self, other: &Self) -> bool {
999 self.user_id() == other.user_id() && self.device_id() == other.device_id()
1000 }
1001}
1002
1003#[cfg(any(test, feature = "testing"))]
1005#[allow(dead_code)]
1006pub(crate) mod testing {
1007 use serde_json::json;
1008
1009 use crate::{identities::DeviceData, types::DeviceKeys};
1010
1011 pub fn device_keys() -> DeviceKeys {
1013 let device_keys = json!({
1014 "algorithms": vec![
1015 "m.olm.v1.curve25519-aes-sha2",
1016 "m.megolm.v1.aes-sha2"
1017 ],
1018 "device_id": "BNYQQWUMXO",
1019 "user_id": "@example:localhost",
1020 "keys": {
1021 "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1022 "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1023 },
1024 "signatures": {
1025 "@example:localhost": {
1026 "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1027 }
1028 },
1029 "unsigned": {
1030 "device_display_name": "Alice's mobile phone"
1031 }
1032 });
1033
1034 serde_json::from_value(device_keys).unwrap()
1035 }
1036
1037 pub fn get_device() -> DeviceData {
1039 let device_keys = device_keys();
1040 DeviceData::try_from(&device_keys).unwrap()
1041 }
1042}
1043
1044#[cfg(test)]
1045pub(crate) mod tests {
1046 use ruma::{user_id, MilliSecondsSinceUnixEpoch};
1047 use serde_json::json;
1048 use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
1049
1050 use super::testing::{device_keys, get_device};
1051 use crate::{identities::LocalTrust, DeviceData};
1052
1053 #[test]
1054 fn create_a_device() {
1055 let now = MilliSecondsSinceUnixEpoch::now();
1056 let user_id = user_id!("@example:localhost");
1057 let device_id = "BNYQQWUMXO";
1058
1059 let device = get_device();
1060
1061 assert_eq!(user_id, device.user_id());
1062 assert_eq!(device_id, device.device_id());
1063 assert_eq!(device.algorithms().len(), 2);
1064 assert_eq!(LocalTrust::Unset, device.local_trust_state());
1065 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1066 assert_eq!(
1067 device.curve25519_key().unwrap(),
1068 Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1069 .unwrap(),
1070 );
1071 assert_eq!(
1072 device.ed25519_key().unwrap(),
1073 Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1074 );
1075
1076 let then = MilliSecondsSinceUnixEpoch::now();
1077
1078 assert!(device.first_time_seen_ts() >= now);
1079 assert!(device.first_time_seen_ts() <= then);
1080 }
1081
1082 #[test]
1083 fn update_a_device() {
1084 let mut device = get_device();
1085
1086 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1087
1088 let display_name = "Alice's work computer".to_owned();
1089
1090 let mut device_keys = device_keys();
1091 device_keys.unsigned.device_display_name = Some(display_name.clone());
1092 assert!(device.update_device(&device_keys).unwrap());
1093 assert_eq!(&display_name, device.display_name().as_ref().unwrap());
1094
1095 assert!(!device.update_device(&device_keys).unwrap());
1097 }
1098
1099 #[test]
1100 #[allow(clippy::redundant_clone)]
1101 fn delete_a_device() {
1102 let device = get_device();
1103 assert!(!device.is_deleted());
1104
1105 let device_clone = device.clone();
1106
1107 device.mark_as_deleted();
1108 assert!(device.is_deleted());
1109 assert!(device_clone.is_deleted());
1110 }
1111
1112 #[test]
1113 fn deserialize_device() {
1114 let user_id = user_id!("@example:localhost");
1115 let device_id = "BNYQQWUMXO";
1116
1117 let device = json!({
1118 "inner": {
1119 "user_id": user_id,
1120 "device_id": device_id,
1121 "algorithms": ["m.olm.v1.curve25519-aes-sha2","m.megolm.v1.aes-sha2"],
1122 "keys": {
1123 "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1124 "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1125 },
1126 "signatures": {
1127 "@example:localhost": {
1128 "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1129 }
1130 },
1131 "unsigned": {
1132 "device_display_name": "Alice's mobile phone"
1133 }
1134 },
1135 "deleted": false,
1136 "trust_state": "Verified",
1137 "withheld_code_sent": false,
1138 "first_time_seen_ts": 1696931068314u64
1139 });
1140
1141 let device: DeviceData =
1142 serde_json::from_value(device).expect("We should be able to deserialize our device");
1143
1144 assert_eq!(user_id, device.user_id());
1145 assert_eq!(device_id, device.device_id());
1146 assert_eq!(device.algorithms().len(), 2);
1147 assert_eq!(LocalTrust::Verified, device.local_trust_state());
1148 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1149 assert_eq!(
1150 device.curve25519_key().unwrap(),
1151 Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1152 .unwrap(),
1153 );
1154 assert_eq!(
1155 device.ed25519_key().unwrap(),
1156 Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1157 );
1158 }
1159}