1use std::{
16 collections::{BTreeMap, HashMap},
17 ops::Deref,
18 sync::{
19 atomic::{AtomicBool, Ordering},
20 Arc,
21 },
22};
23
24use matrix_sdk_common::{deserialized_responses::WithheldCode, 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: Session,
64 share_info: ShareInfo,
65 message: Raw<AnyToDeviceEventContent>,
66 },
67 Withheld {
68 code: WithheldCode,
69 },
70}
71
72#[derive(Clone, Serialize, Deserialize)]
74pub struct DeviceData {
75 #[serde(alias = "inner")]
76 pub(crate) device_keys: Arc<DeviceKeys>,
77 #[serde(
78 serialize_with = "atomic_bool_serializer",
79 deserialize_with = "atomic_bool_deserializer"
80 )]
81 deleted: Arc<AtomicBool>,
82 trust_state: Arc<RwLock<LocalTrust>>,
83 #[serde(
86 default,
87 serialize_with = "atomic_bool_serializer",
88 deserialize_with = "atomic_bool_deserializer"
89 )]
90 withheld_code_sent: Arc<AtomicBool>,
91 #[serde(default = "default_timestamp")]
94 first_time_seen_ts: MilliSecondsSinceUnixEpoch,
95 #[serde(default)]
98 pub(crate) olm_wedging_index: SequenceNumber,
99}
100
101fn default_timestamp() -> MilliSecondsSinceUnixEpoch {
102 MilliSecondsSinceUnixEpoch(UInt::default())
103}
104
105#[cfg(not(tarpaulin_include))]
106impl std::fmt::Debug for DeviceData {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 f.debug_struct("DeviceData")
109 .field("user_id", &self.user_id())
110 .field("device_id", &self.device_id())
111 .field("display_name", &self.display_name())
112 .field("keys", self.keys())
113 .field("deleted", &self.deleted.load(Ordering::SeqCst))
114 .field("trust_state", &self.trust_state)
115 .field("withheld_code_sent", &self.withheld_code_sent)
116 .finish()
117 }
118}
119
120#[derive(Clone)]
122pub struct Device {
123 pub(crate) inner: DeviceData,
124 pub(crate) verification_machine: VerificationMachine,
125 pub(crate) own_identity: Option<OwnUserIdentityData>,
126 pub(crate) device_owner_identity: Option<UserIdentityData>,
127}
128
129#[cfg(not(tarpaulin_include))]
130impl std::fmt::Debug for Device {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 f.debug_struct("Device").field("device", &self.inner).finish()
133 }
134}
135
136impl Deref for Device {
137 type Target = DeviceData;
138
139 fn deref(&self) -> &Self::Target {
140 &self.inner
141 }
142}
143
144impl Device {
145 pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
155 let (sas, request) = self.verification_machine.start_sas(self.inner.clone()).await?;
156
157 if let OutgoingVerificationRequest::ToDevice(r) = request {
158 Ok((sas, r))
159 } else {
160 panic!("Invalid verification request type");
161 }
162 }
163
164 pub fn is_our_own_device(&self) -> bool {
166 let own_ed25519_key = self.verification_machine.store.account.identity_keys.ed25519;
167 let own_curve25519_key = self.verification_machine.store.account.identity_keys.curve25519;
168
169 self.user_id() == self.verification_machine.own_user_id()
170 && self.device_id() == self.verification_machine.own_device_id()
171 && self.ed25519_key().is_some_and(|k| k == own_ed25519_key)
172 && self.curve25519_key().is_some_and(|k| k == own_curve25519_key)
173 }
174
175 pub fn is_owner_of_session(
181 &self,
182 session: &InboundGroupSession,
183 ) -> Result<bool, MismatchedIdentityKeysError> {
184 if session.has_been_imported() {
185 Ok(false)
205 } else if let Some(key) =
206 session.signing_keys().get(&DeviceKeyAlgorithm::Ed25519).and_then(|k| k.ed25519())
207 {
208 let ed25519_comparison = self.ed25519_key().map(|k| k == key);
267 let curve25519_comparison = self.curve25519_key().map(|k| k == session.sender_key());
268
269 match (ed25519_comparison, curve25519_comparison) {
270 (_, Some(false)) | (Some(false), _) => Err(MismatchedIdentityKeysError {
273 key_ed25519: key.into(),
274 device_ed25519: self.ed25519_key().map(Into::into),
275 key_curve25519: session.sender_key().into(),
276 device_curve25519: self.curve25519_key().map(Into::into),
277 }),
278 (Some(true), Some(true)) => Ok(true),
280 _ => Ok(false),
283 }
284 } else {
285 Ok(false)
286 }
287 }
288
289 pub fn is_cross_signed_by_owner(&self) -> bool {
291 self.device_owner_identity
292 .as_ref()
293 .is_some_and(|owner_identity| self.inner.is_cross_signed_by_owner(owner_identity))
294 }
295
296 pub fn is_device_owner_verified(&self) -> bool {
298 self.device_owner_identity.as_ref().is_some_and(|id| match id {
299 UserIdentityData::Own(own_identity) => own_identity.is_verified(),
300 UserIdentityData::Other(other_identity) => {
301 self.own_identity.as_ref().is_some_and(|oi| oi.is_identity_verified(other_identity))
302 }
303 })
304 }
305
306 pub fn request_verification(&self) -> (VerificationRequest, OutgoingVerificationRequest) {
311 self.request_verification_helper(None)
312 }
313
314 pub fn request_verification_with_methods(
323 &self,
324 methods: Vec<VerificationMethod>,
325 ) -> (VerificationRequest, OutgoingVerificationRequest) {
326 self.request_verification_helper(Some(methods))
327 }
328
329 fn request_verification_helper(
330 &self,
331 methods: Option<Vec<VerificationMethod>>,
332 ) -> (VerificationRequest, OutgoingVerificationRequest) {
333 self.verification_machine.request_to_device_verification(
334 self.user_id(),
335 vec![self.device_id().to_owned()],
336 methods,
337 )
338 }
339
340 pub(crate) async fn get_most_recent_session(&self) -> OlmResult<Option<Session>> {
342 self.inner.get_most_recent_session(self.verification_machine.store.inner()).await
343 }
344
345 pub fn is_verified(&self) -> bool {
353 self.inner.is_verified(&self.own_identity, &self.device_owner_identity)
354 }
355
356 pub fn is_cross_signing_trusted(&self) -> bool {
358 self.inner.is_cross_signing_trusted(&self.own_identity, &self.device_owner_identity)
359 }
360
361 pub async fn verify(&self) -> Result<SignatureUploadRequest, SignatureError> {
375 if self.user_id() == self.verification_machine.own_user_id() {
376 Ok(self
377 .verification_machine
378 .store
379 .private_identity
380 .lock()
381 .await
382 .sign_device(&self.inner)
383 .await?)
384 } else {
385 Err(SignatureError::UserIdMismatch)
386 }
387 }
388
389 pub async fn set_local_trust(&self, trust_state: LocalTrust) -> StoreResult<()> {
398 self.inner.set_trust_state(trust_state);
399
400 let changes = Changes {
401 devices: DeviceChanges { changed: vec![self.inner.clone()], ..Default::default() },
402 ..Default::default()
403 };
404
405 self.verification_machine.store.save_changes(changes).await
406 }
407
408 pub(crate) async fn encrypt(
414 &self,
415 event_type: &str,
416 content: impl Serialize,
417 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
418 self.inner.encrypt(self.verification_machine.store.inner(), event_type, content).await
419 }
420
421 pub async fn encrypt_room_key_for_forwarding(
424 &self,
425 session: InboundGroupSession,
426 message_index: Option<u32>,
427 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
428 let content: ForwardedRoomKeyContent = {
429 let export = if let Some(index) = message_index {
430 session.export_at_index(index).await
431 } else {
432 session.export().await
433 };
434
435 export.try_into()?
436 };
437
438 let event_type = content.event_type().to_owned();
439
440 self.encrypt(&event_type, content).await
441 }
442
443 pub async fn encrypt_event_raw(
468 &self,
469 event_type: &str,
470 content: &Value,
471 ) -> OlmResult<Raw<ToDeviceEncryptedEventContent>> {
472 let (used_session, raw_encrypted) = self.encrypt(event_type, content).await?;
473
474 self.verification_machine
476 .store
477 .save_changes(Changes { sessions: vec![used_session], ..Default::default() })
478 .await?;
479
480 Ok(raw_encrypted)
481 }
482
483 pub fn is_dehydrated(&self) -> bool {
485 self.inner.is_dehydrated()
486 }
487}
488
489#[derive(Debug)]
491pub struct UserDevices {
492 pub(crate) inner: HashMap<OwnedDeviceId, DeviceData>,
493 pub(crate) verification_machine: VerificationMachine,
494 pub(crate) own_identity: Option<OwnUserIdentityData>,
495 pub(crate) device_owner_identity: Option<UserIdentityData>,
496}
497
498impl UserDevices {
499 pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
501 self.inner.get(device_id).map(|d| Device {
502 inner: d.clone(),
503 verification_machine: self.verification_machine.clone(),
504 own_identity: self.own_identity.clone(),
505 device_owner_identity: self.device_owner_identity.clone(),
506 })
507 }
508
509 fn own_user_id(&self) -> &UserId {
510 self.verification_machine.own_user_id()
511 }
512
513 fn own_device_id(&self) -> &DeviceId {
514 self.verification_machine.own_device_id()
515 }
516
517 pub fn is_any_verified(&self) -> bool {
523 self.inner
524 .values()
525 .filter(|d| {
526 !(d.user_id() == self.own_user_id() && d.device_id() == self.own_device_id())
527 })
528 .any(|d| d.is_verified(&self.own_identity, &self.device_owner_identity))
529 }
530
531 pub fn keys(&self) -> impl Iterator<Item = &DeviceId> {
533 self.inner.keys().map(Deref::deref)
534 }
535
536 pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
538 self.inner.values().map(move |d| Device {
539 inner: d.clone(),
540 verification_machine: self.verification_machine.clone(),
541 own_identity: self.own_identity.clone(),
542 device_owner_identity: self.device_owner_identity.clone(),
543 })
544 }
545}
546
547#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
549#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
550pub enum LocalTrust {
551 Verified = 0,
553 BlackListed = 1,
555 Ignored = 2,
557 Unset = 3,
559}
560
561impl From<i64> for LocalTrust {
562 fn from(state: i64) -> Self {
563 match state {
564 0 => LocalTrust::Verified,
565 1 => LocalTrust::BlackListed,
566 2 => LocalTrust::Ignored,
567 3 => LocalTrust::Unset,
568 _ => LocalTrust::Unset,
569 }
570 }
571}
572
573impl DeviceData {
574 pub fn new(device_keys: DeviceKeys, trust_state: LocalTrust) -> Self {
578 Self {
579 device_keys: device_keys.into(),
580 trust_state: Arc::new(RwLock::new(trust_state)),
581 deleted: Arc::new(AtomicBool::new(false)),
582 withheld_code_sent: Arc::new(AtomicBool::new(false)),
583 first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
584 olm_wedging_index: Default::default(),
585 }
586 }
587
588 pub fn user_id(&self) -> &UserId {
590 &self.device_keys.user_id
591 }
592
593 pub fn device_id(&self) -> &DeviceId {
595 &self.device_keys.device_id
596 }
597
598 pub fn display_name(&self) -> Option<&str> {
600 self.device_keys.unsigned.device_display_name.as_deref()
601 }
602
603 pub fn get_key(&self, algorithm: DeviceKeyAlgorithm) -> Option<&DeviceKey> {
605 self.device_keys.get_key(algorithm)
606 }
607
608 pub fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
610 self.device_keys.curve25519_key()
611 }
612
613 pub fn ed25519_key(&self) -> Option<Ed25519PublicKey> {
615 self.device_keys.ed25519_key()
616 }
617
618 pub fn keys(&self) -> &BTreeMap<OwnedDeviceKeyId, DeviceKey> {
620 &self.device_keys.keys
621 }
622
623 pub fn signatures(&self) -> &Signatures {
625 &self.device_keys.signatures
626 }
627
628 pub fn local_trust_state(&self) -> LocalTrust {
630 *self.trust_state.read()
631 }
632
633 pub fn is_locally_trusted(&self) -> bool {
635 self.local_trust_state() == LocalTrust::Verified
636 }
637
638 pub fn is_blacklisted(&self) -> bool {
642 self.local_trust_state() == LocalTrust::BlackListed
643 }
644
645 pub(crate) fn set_trust_state(&self, state: LocalTrust) {
650 *self.trust_state.write() = state;
651 }
652
653 pub(crate) fn mark_withheld_code_as_sent(&self) {
654 self.withheld_code_sent.store(true, Ordering::Relaxed)
655 }
656
657 pub fn was_withheld_code_sent(&self) -> bool {
660 self.withheld_code_sent.load(Ordering::Relaxed)
661 }
662
663 pub fn algorithms(&self) -> &[EventEncryptionAlgorithm] {
665 &self.device_keys.algorithms
666 }
667
668 pub fn supports_olm(&self) -> bool {
670 #[cfg(feature = "experimental-algorithms")]
671 {
672 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
673 || self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
674 }
675
676 #[cfg(not(feature = "experimental-algorithms"))]
677 {
678 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
679 }
680 }
681
682 pub(crate) async fn get_most_recent_session(
685 &self,
686 store: &CryptoStoreWrapper,
687 ) -> OlmResult<Option<Session>> {
688 if let Some(sender_key) = self.curve25519_key() {
689 if let Some(sessions) = store.get_sessions(&sender_key.to_base64()).await? {
690 let mut sessions = sessions.lock().await;
691 sessions.sort_by_key(|s| s.creation_time);
692
693 Ok(sessions.last().cloned())
694 } else {
695 Ok(None)
696 }
697 } else {
698 Ok(None)
699 }
700 }
701
702 #[cfg(feature = "experimental-algorithms")]
705 pub fn supports_olm_v2(&self) -> bool {
706 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
707 }
708
709 pub fn olm_session_config(&self) -> SessionConfig {
711 #[cfg(feature = "experimental-algorithms")]
712 if self.supports_olm_v2() {
713 SessionConfig::version_2()
714 } else {
715 SessionConfig::version_1()
716 }
717
718 #[cfg(not(feature = "experimental-algorithms"))]
719 SessionConfig::version_1()
720 }
721
722 pub fn is_deleted(&self) -> bool {
724 self.deleted.load(Ordering::Relaxed)
725 }
726
727 pub(crate) fn is_verified(
728 &self,
729 own_identity: &Option<OwnUserIdentityData>,
730 device_owner: &Option<UserIdentityData>,
731 ) -> bool {
732 self.is_locally_trusted() || self.is_cross_signing_trusted(own_identity, device_owner)
733 }
734
735 pub(crate) fn is_cross_signing_trusted(
736 &self,
737 own_identity: &Option<OwnUserIdentityData>,
738 device_owner: &Option<UserIdentityData>,
739 ) -> bool {
740 own_identity.as_ref().zip(device_owner.as_ref()).is_some_and(
741 |(own_identity, device_identity)| {
742 match device_identity {
743 UserIdentityData::Own(_) => {
744 own_identity.is_verified() && own_identity.is_device_signed(self)
745 }
746
747 UserIdentityData::Other(device_identity) => {
751 own_identity.is_identity_verified(device_identity)
752 && device_identity.is_device_signed(self)
753 }
754 }
755 },
756 )
757 }
758
759 pub(crate) fn is_cross_signed_by_owner(
760 &self,
761 device_owner_identity: &UserIdentityData,
762 ) -> bool {
763 match device_owner_identity {
764 UserIdentityData::Own(identity) => identity.is_device_signed(self),
767 UserIdentityData::Other(device_identity) => device_identity.is_device_signed(self),
770 }
771 }
772
773 #[instrument(
791 skip_all,
792 fields(
793 recipient = ?self.user_id(),
794 recipient_device = ?self.device_id(),
795 recipient_key = ?self.curve25519_key(),
796 event_type,
797 message_id,
798 ))
799 ]
800 pub(crate) async fn encrypt(
801 &self,
802 store: &CryptoStoreWrapper,
803 event_type: &str,
804 content: impl Serialize,
805 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
806 #[cfg(not(target_arch = "wasm32"))]
807 let message_id = ulid::Ulid::new().to_string();
808 #[cfg(target_arch = "wasm32")]
809 let message_id = ruma::TransactionId::new().to_string();
810
811 tracing::Span::current().record("message_id", &message_id);
812
813 let session = self.get_most_recent_session(store).await?;
814
815 if let Some(mut session) = session {
816 let message = session.encrypt(self, event_type, content, Some(message_id)).await?;
817 Ok((session, message))
818 } else {
819 trace!("Trying to encrypt an event for a device, but no Olm session is found.");
820 Err(OlmError::MissingSession)
821 }
822 }
823
824 pub(crate) async fn maybe_encrypt_room_key(
825 &self,
826 store: &CryptoStoreWrapper,
827 session: OutboundGroupSession,
828 ) -> OlmResult<MaybeEncryptedRoomKey> {
829 let content = session.as_content().await;
830 let message_index = session.message_index().await;
831 let event_type = content.event_type().to_owned();
832
833 match self.encrypt(store, &event_type, content).await {
834 Ok((session, encrypted)) => Ok(MaybeEncryptedRoomKey::Encrypted {
835 share_info: ShareInfo::new_shared(
836 session.sender_key().to_owned(),
837 message_index,
838 self.olm_wedging_index,
839 ),
840 used_session: session,
841 message: encrypted.cast(),
842 }),
843
844 Err(OlmError::MissingSession) => {
845 Ok(MaybeEncryptedRoomKey::Withheld { code: WithheldCode::NoOlm })
846 }
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}