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 session_manager::{withheld_code_for_device_for_share_strategy, CollectStrategy},
47 store::{
48 caches::SequenceNumber,
49 types::{Changes, DeviceChanges},
50 CryptoStoreWrapper, Result as StoreResult,
51 },
52 types::{
53 events::{
54 forwarded_room_key::ForwardedRoomKeyContent,
55 room::encrypted::ToDeviceEncryptedEventContent, EventType,
56 },
57 requests::{OutgoingVerificationRequest, ToDeviceRequest},
58 DeviceKey, DeviceKeys, EventEncryptionAlgorithm, Signatures, SignedKey,
59 },
60 verification::VerificationMachine,
61 Account, Sas, VerificationRequest,
62};
63
64pub enum MaybeEncryptedRoomKey {
65 Encrypted {
66 used_session: Box<Session>,
68 share_info: Box<ShareInfo>,
70 message: Raw<AnyToDeviceEventContent>,
71 },
72 MissingSession,
75}
76
77#[derive(Clone, Serialize, Deserialize)]
79pub struct DeviceData {
80 #[serde(alias = "inner")]
81 pub(crate) device_keys: Arc<DeviceKeys>,
82 #[serde(
83 serialize_with = "atomic_bool_serializer",
84 deserialize_with = "atomic_bool_deserializer"
85 )]
86 deleted: Arc<AtomicBool>,
87 trust_state: Arc<RwLock<LocalTrust>>,
88 #[serde(
91 default,
92 serialize_with = "atomic_bool_serializer",
93 deserialize_with = "atomic_bool_deserializer"
94 )]
95 withheld_code_sent: Arc<AtomicBool>,
96 #[serde(default = "default_timestamp")]
99 first_time_seen_ts: MilliSecondsSinceUnixEpoch,
100 #[serde(default)]
103 pub(crate) olm_wedging_index: SequenceNumber,
104}
105
106fn default_timestamp() -> MilliSecondsSinceUnixEpoch {
107 MilliSecondsSinceUnixEpoch(UInt::default())
108}
109
110#[cfg(not(tarpaulin_include))]
111impl std::fmt::Debug for DeviceData {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 f.debug_struct("DeviceData")
114 .field("user_id", &self.user_id())
115 .field("device_id", &self.device_id())
116 .field("display_name", &self.display_name())
117 .field("keys", self.keys())
118 .field("deleted", &self.deleted.load(Ordering::SeqCst))
119 .field("trust_state", &self.trust_state)
120 .field("withheld_code_sent", &self.withheld_code_sent)
121 .finish()
122 }
123}
124
125#[derive(Clone)]
127pub struct Device {
128 pub(crate) inner: DeviceData,
129 pub(crate) verification_machine: VerificationMachine,
130 pub(crate) own_identity: Option<OwnUserIdentityData>,
131 pub(crate) device_owner_identity: Option<UserIdentityData>,
132}
133
134#[cfg(not(tarpaulin_include))]
135impl std::fmt::Debug for Device {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 f.debug_struct("Device").field("device", &self.inner).finish()
138 }
139}
140
141impl Deref for Device {
142 type Target = DeviceData;
143
144 fn deref(&self) -> &Self::Target {
145 &self.inner
146 }
147}
148
149impl Device {
150 pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
160 let (sas, request) = self.verification_machine.start_sas(self.inner.clone()).await?;
161
162 if let OutgoingVerificationRequest::ToDevice(r) = request {
163 Ok((sas, r))
164 } else {
165 panic!("Invalid verification request type");
166 }
167 }
168
169 pub fn is_our_own_device(&self) -> bool {
171 let own_ed25519_key = self.verification_machine.store.account.identity_keys.ed25519;
172 let own_curve25519_key = self.verification_machine.store.account.identity_keys.curve25519;
173
174 self.user_id() == self.verification_machine.own_user_id()
175 && self.device_id() == self.verification_machine.own_device_id()
176 && self.ed25519_key().is_some_and(|k| k == own_ed25519_key)
177 && self.curve25519_key().is_some_and(|k| k == own_curve25519_key)
178 }
179
180 pub fn is_owner_of_session(
186 &self,
187 session: &InboundGroupSession,
188 ) -> Result<bool, MismatchedIdentityKeysError> {
189 if session.has_been_imported() {
190 Ok(false)
210 } else if let Some(key) =
211 session.signing_keys().get(&DeviceKeyAlgorithm::Ed25519).and_then(|k| k.ed25519())
212 {
213 let ed25519_comparison = self.ed25519_key().map(|k| k == key);
272 let curve25519_comparison = self.curve25519_key().map(|k| k == session.sender_key());
273
274 match (ed25519_comparison, curve25519_comparison) {
275 (_, Some(false)) | (Some(false), _) => Err(MismatchedIdentityKeysError {
278 key_ed25519: key.into(),
279 device_ed25519: self.ed25519_key().map(Into::into),
280 key_curve25519: session.sender_key().into(),
281 device_curve25519: self.curve25519_key().map(Into::into),
282 }),
283 (Some(true), Some(true)) => Ok(true),
285 _ => Ok(false),
288 }
289 } else {
290 Ok(false)
291 }
292 }
293
294 pub fn is_cross_signed_by_owner(&self) -> bool {
296 self.device_owner_identity
297 .as_ref()
298 .is_some_and(|owner_identity| self.inner.is_cross_signed_by_owner(owner_identity))
299 }
300
301 pub fn is_device_owner_verified(&self) -> bool {
303 self.device_owner_identity.as_ref().is_some_and(|id| match id {
304 UserIdentityData::Own(own_identity) => own_identity.is_verified(),
305 UserIdentityData::Other(other_identity) => {
306 self.own_identity.as_ref().is_some_and(|oi| oi.is_identity_verified(other_identity))
307 }
308 })
309 }
310
311 pub fn request_verification(&self) -> (VerificationRequest, OutgoingVerificationRequest) {
316 self.request_verification_helper(None)
317 }
318
319 pub fn request_verification_with_methods(
328 &self,
329 methods: Vec<VerificationMethod>,
330 ) -> (VerificationRequest, OutgoingVerificationRequest) {
331 self.request_verification_helper(Some(methods))
332 }
333
334 fn request_verification_helper(
335 &self,
336 methods: Option<Vec<VerificationMethod>>,
337 ) -> (VerificationRequest, OutgoingVerificationRequest) {
338 self.verification_machine.request_to_device_verification(
339 self.user_id(),
340 vec![self.device_id().to_owned()],
341 methods,
342 )
343 }
344
345 pub(crate) async fn get_most_recent_session(&self) -> OlmResult<Option<Session>> {
347 self.inner.get_most_recent_session(self.verification_machine.store.inner()).await
348 }
349
350 pub fn is_verified(&self) -> bool {
358 self.inner.is_verified(&self.own_identity, &self.device_owner_identity)
359 }
360
361 pub fn is_cross_signing_trusted(&self) -> bool {
363 self.inner.is_cross_signing_trusted(&self.own_identity, &self.device_owner_identity)
364 }
365
366 pub async fn verify(&self) -> Result<SignatureUploadRequest, SignatureError> {
380 if self.user_id() == self.verification_machine.own_user_id() {
381 Ok(self
382 .verification_machine
383 .store
384 .private_identity
385 .lock()
386 .await
387 .sign_device(&self.inner)
388 .await?)
389 } else {
390 Err(SignatureError::UserIdMismatch)
391 }
392 }
393
394 pub async fn set_local_trust(&self, trust_state: LocalTrust) -> StoreResult<()> {
403 self.inner.set_trust_state(trust_state);
404
405 let changes = Changes {
406 devices: DeviceChanges { changed: vec![self.inner.clone()], ..Default::default() },
407 ..Default::default()
408 };
409
410 self.verification_machine.store.save_changes(changes).await
411 }
412
413 pub(crate) async fn encrypt(
419 &self,
420 event_type: &str,
421 content: impl Serialize,
422 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
423 self.inner.encrypt(self.verification_machine.store.inner(), event_type, content).await
424 }
425
426 pub async fn encrypt_room_key_for_forwarding(
429 &self,
430 session: InboundGroupSession,
431 message_index: Option<u32>,
432 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
433 let content: ForwardedRoomKeyContent = {
434 let export = if let Some(index) = message_index {
435 session.export_at_index(index).await
436 } else {
437 session.export().await
438 };
439
440 export.try_into()?
441 };
442
443 let event_type = content.event_type().to_owned();
444
445 self.encrypt(&event_type, content).await
446 }
447
448 pub async fn encrypt_event_raw(
475 &self,
476 event_type: &str,
477 content: &Value,
478 share_strategy: CollectStrategy,
479 ) -> OlmResult<Raw<ToDeviceEncryptedEventContent>> {
480 if let Some(withheld_code) = withheld_code_for_device_for_share_strategy(
481 &self.inner,
482 share_strategy,
483 &self.own_identity,
484 &self.device_owner_identity,
485 )
486 .await?
487 {
488 return Err(OlmError::Withheld(withheld_code));
489 }
490
491 let (used_session, raw_encrypted) = self.encrypt(event_type, content).await?;
492
493 self.verification_machine
495 .store
496 .save_changes(Changes { sessions: vec![used_session], ..Default::default() })
497 .await?;
498
499 Ok(raw_encrypted)
500 }
501
502 pub fn is_dehydrated(&self) -> bool {
504 self.inner.is_dehydrated()
505 }
506}
507
508#[derive(Debug)]
510pub struct UserDevices {
511 pub(crate) inner: HashMap<OwnedDeviceId, DeviceData>,
512 pub(crate) verification_machine: VerificationMachine,
513 pub(crate) own_identity: Option<OwnUserIdentityData>,
514 pub(crate) device_owner_identity: Option<UserIdentityData>,
515}
516
517impl UserDevices {
518 pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
520 self.inner.get(device_id).map(|d| Device {
521 inner: d.clone(),
522 verification_machine: self.verification_machine.clone(),
523 own_identity: self.own_identity.clone(),
524 device_owner_identity: self.device_owner_identity.clone(),
525 })
526 }
527
528 fn own_user_id(&self) -> &UserId {
529 self.verification_machine.own_user_id()
530 }
531
532 fn own_device_id(&self) -> &DeviceId {
533 self.verification_machine.own_device_id()
534 }
535
536 pub fn is_any_verified(&self) -> bool {
542 self.inner
543 .values()
544 .filter(|d| {
545 !(d.user_id() == self.own_user_id() && d.device_id() == self.own_device_id())
546 })
547 .any(|d| d.is_verified(&self.own_identity, &self.device_owner_identity))
548 }
549
550 pub fn keys(&self) -> impl Iterator<Item = &DeviceId> {
552 self.inner.keys().map(Deref::deref)
553 }
554
555 pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
557 self.inner.values().map(move |d| Device {
558 inner: d.clone(),
559 verification_machine: self.verification_machine.clone(),
560 own_identity: self.own_identity.clone(),
561 device_owner_identity: self.device_owner_identity.clone(),
562 })
563 }
564}
565
566#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
568#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
569pub enum LocalTrust {
570 Verified = 0,
572 BlackListed = 1,
574 Ignored = 2,
576 Unset = 3,
578}
579
580impl From<i64> for LocalTrust {
581 fn from(state: i64) -> Self {
582 match state {
583 0 => LocalTrust::Verified,
584 1 => LocalTrust::BlackListed,
585 2 => LocalTrust::Ignored,
586 3 => LocalTrust::Unset,
587 _ => LocalTrust::Unset,
588 }
589 }
590}
591
592impl DeviceData {
593 pub fn new(device_keys: DeviceKeys, trust_state: LocalTrust) -> Self {
597 Self {
598 device_keys: device_keys.into(),
599 trust_state: Arc::new(RwLock::new(trust_state)),
600 deleted: Arc::new(AtomicBool::new(false)),
601 withheld_code_sent: Arc::new(AtomicBool::new(false)),
602 first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
603 olm_wedging_index: Default::default(),
604 }
605 }
606
607 pub fn user_id(&self) -> &UserId {
609 &self.device_keys.user_id
610 }
611
612 pub fn device_id(&self) -> &DeviceId {
614 &self.device_keys.device_id
615 }
616
617 pub fn display_name(&self) -> Option<&str> {
619 self.device_keys.unsigned.device_display_name.as_deref()
620 }
621
622 pub fn get_key(&self, algorithm: DeviceKeyAlgorithm) -> Option<&DeviceKey> {
624 self.device_keys.get_key(algorithm)
625 }
626
627 pub fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
629 self.device_keys.curve25519_key()
630 }
631
632 pub fn ed25519_key(&self) -> Option<Ed25519PublicKey> {
634 self.device_keys.ed25519_key()
635 }
636
637 pub fn keys(&self) -> &BTreeMap<OwnedDeviceKeyId, DeviceKey> {
639 &self.device_keys.keys
640 }
641
642 pub fn signatures(&self) -> &Signatures {
644 &self.device_keys.signatures
645 }
646
647 pub fn local_trust_state(&self) -> LocalTrust {
649 *self.trust_state.read()
650 }
651
652 pub fn is_locally_trusted(&self) -> bool {
654 self.local_trust_state() == LocalTrust::Verified
655 }
656
657 pub fn is_blacklisted(&self) -> bool {
661 self.local_trust_state() == LocalTrust::BlackListed
662 }
663
664 pub(crate) fn set_trust_state(&self, state: LocalTrust) {
669 *self.trust_state.write() = state;
670 }
671
672 pub(crate) fn mark_withheld_code_as_sent(&self) {
673 self.withheld_code_sent.store(true, Ordering::Relaxed)
674 }
675
676 pub fn was_withheld_code_sent(&self) -> bool {
679 self.withheld_code_sent.load(Ordering::Relaxed)
680 }
681
682 pub fn algorithms(&self) -> &[EventEncryptionAlgorithm] {
684 &self.device_keys.algorithms
685 }
686
687 pub fn supports_olm(&self) -> bool {
689 #[cfg(feature = "experimental-algorithms")]
690 {
691 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
692 || self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
693 }
694
695 #[cfg(not(feature = "experimental-algorithms"))]
696 {
697 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
698 }
699 }
700
701 pub(crate) async fn get_most_recent_session(
704 &self,
705 store: &CryptoStoreWrapper,
706 ) -> OlmResult<Option<Session>> {
707 if let Some(sender_key) = self.curve25519_key() {
708 if let Some(sessions) = store.get_sessions(&sender_key.to_base64()).await? {
709 let mut sessions = sessions.lock().await;
710 sessions.sort_by_key(|s| s.creation_time);
711
712 Ok(sessions.last().cloned())
713 } else {
714 Ok(None)
715 }
716 } else {
717 Ok(None)
718 }
719 }
720
721 #[cfg(feature = "experimental-algorithms")]
724 pub fn supports_olm_v2(&self) -> bool {
725 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
726 }
727
728 pub fn olm_session_config(&self) -> SessionConfig {
730 #[cfg(feature = "experimental-algorithms")]
731 if self.supports_olm_v2() {
732 SessionConfig::version_2()
733 } else {
734 SessionConfig::version_1()
735 }
736
737 #[cfg(not(feature = "experimental-algorithms"))]
738 SessionConfig::version_1()
739 }
740
741 pub fn is_deleted(&self) -> bool {
743 self.deleted.load(Ordering::Relaxed)
744 }
745
746 pub(crate) fn is_verified(
747 &self,
748 own_identity: &Option<OwnUserIdentityData>,
749 device_owner: &Option<UserIdentityData>,
750 ) -> bool {
751 self.is_locally_trusted() || self.is_cross_signing_trusted(own_identity, device_owner)
752 }
753
754 pub(crate) fn is_cross_signing_trusted(
755 &self,
756 own_identity: &Option<OwnUserIdentityData>,
757 device_owner: &Option<UserIdentityData>,
758 ) -> bool {
759 own_identity.as_ref().zip(device_owner.as_ref()).is_some_and(
760 |(own_identity, device_identity)| {
761 match device_identity {
762 UserIdentityData::Own(_) => {
763 own_identity.is_verified() && own_identity.is_device_signed(self)
764 }
765
766 UserIdentityData::Other(device_identity) => {
770 own_identity.is_identity_verified(device_identity)
771 && device_identity.is_device_signed(self)
772 }
773 }
774 },
775 )
776 }
777
778 pub(crate) fn is_cross_signed_by_owner(
779 &self,
780 device_owner_identity: &UserIdentityData,
781 ) -> bool {
782 match device_owner_identity {
783 UserIdentityData::Own(identity) => identity.is_device_signed(self),
786 UserIdentityData::Other(device_identity) => device_identity.is_device_signed(self),
789 }
790 }
791
792 #[instrument(
810 skip_all,
811 fields(
812 recipient = ?self.user_id(),
813 recipient_device = ?self.device_id(),
814 recipient_key = ?self.curve25519_key(),
815 event_type,
816 message_id,
817 ))
818 ]
819 pub(crate) async fn encrypt(
820 &self,
821 store: &CryptoStoreWrapper,
822 event_type: &str,
823 content: impl Serialize,
824 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
825 #[cfg(not(target_family = "wasm"))]
826 let message_id = ulid::Ulid::new().to_string();
827 #[cfg(target_family = "wasm")]
828 let message_id = ruma::TransactionId::new().to_string();
829
830 tracing::Span::current().record("message_id", &message_id);
831
832 let session = self.get_most_recent_session(store).await?;
833
834 if let Some(mut session) = session {
835 let message = session.encrypt(self, event_type, content, Some(message_id)).await?;
836 Ok((session, message))
837 } else {
838 trace!("Trying to encrypt an event for a device, but no Olm session is found.");
839 Err(OlmError::MissingSession)
840 }
841 }
842
843 pub(crate) async fn maybe_encrypt_room_key(
844 &self,
845 store: &CryptoStoreWrapper,
846 session: OutboundGroupSession,
847 ) -> OlmResult<MaybeEncryptedRoomKey> {
848 let content = session.as_content().await;
849 let message_index = session.message_index().await;
850 let event_type = content.event_type().to_owned();
851
852 match self.encrypt(store, &event_type, content).await {
853 Ok((session, encrypted)) => Ok(MaybeEncryptedRoomKey::Encrypted {
854 share_info: Box::new(ShareInfo::new_shared(
855 session.sender_key().to_owned(),
856 message_index,
857 self.olm_wedging_index,
858 )),
859 used_session: Box::new(session),
860 message: encrypted.cast(),
861 }),
862
863 Err(OlmError::MissingSession) => Ok(MaybeEncryptedRoomKey::MissingSession),
864 Err(e) => Err(e),
865 }
866 }
867
868 pub(crate) fn update_device(
872 &mut self,
873 device_keys: &DeviceKeys,
874 ) -> Result<bool, SignatureError> {
875 self.verify_device_keys(device_keys)?;
876
877 if self.user_id() != device_keys.user_id || self.device_id() != device_keys.device_id {
878 Err(SignatureError::UserIdMismatch)
879 } else if self.ed25519_key() != device_keys.ed25519_key() {
880 Err(SignatureError::SigningKeyChanged(
881 self.ed25519_key().map(Box::new),
882 device_keys.ed25519_key().map(Box::new),
883 ))
884 } else if self.device_keys.as_ref() != device_keys {
885 trace!(
886 user_id = ?self.user_id(),
887 device_id = ?self.device_id(),
888 keys = ?self.keys(),
889 "Updated a device",
890 );
891
892 self.device_keys = device_keys.clone().into();
893
894 Ok(true)
895 } else {
896 Ok(false)
898 }
899 }
900
901 pub fn as_device_keys(&self) -> &DeviceKeys {
903 &self.device_keys
904 }
905
906 pub(crate) fn has_signed_raw(
916 &self,
917 signatures: &Signatures,
918 canonical_json: &str,
919 ) -> Result<(), SignatureError> {
920 let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
921 let user_id = self.user_id();
922 let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id());
923
924 key.verify_canonicalized_json(user_id, key_id, signatures, canonical_json)
925 }
926
927 fn has_signed(&self, signed_object: &impl SignedJsonObject) -> Result<(), SignatureError> {
928 let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
929 let user_id = self.user_id();
930 let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id());
931
932 key.verify_json(user_id, key_id, signed_object)
933 }
934
935 pub(crate) fn verify_device_keys(
936 &self,
937 device_keys: &DeviceKeys,
938 ) -> Result<(), SignatureError> {
939 self.has_signed(device_keys)
940 }
941
942 pub(crate) fn verify_one_time_key(
943 &self,
944 one_time_key: &SignedKey,
945 ) -> Result<(), SignatureError> {
946 self.has_signed(one_time_key)
947 }
948
949 pub(crate) fn mark_as_deleted(&self) {
951 self.deleted.store(true, Ordering::Relaxed);
952 }
953
954 #[cfg(any(test, feature = "testing"))]
955 #[allow(dead_code)]
956 pub async fn from_machine_test_helper(
958 machine: &OlmMachine,
959 ) -> Result<DeviceData, crate::CryptoStoreError> {
960 Ok(DeviceData::from_account(&*machine.store().cache().await?.account().await?))
961 }
962
963 pub fn from_account(account: &Account) -> DeviceData {
976 let device_keys = account.device_keys();
977 let mut device = DeviceData::try_from(&device_keys)
978 .expect("Creating a device from our own account should always succeed");
979 device.first_time_seen_ts = account.creation_local_time();
980
981 device
982 }
983
984 pub fn first_time_seen_ts(&self) -> MilliSecondsSinceUnixEpoch {
987 self.first_time_seen_ts
988 }
989
990 pub fn is_dehydrated(&self) -> bool {
992 self.device_keys.dehydrated.unwrap_or(false)
993 }
994}
995
996impl TryFrom<&DeviceKeys> for DeviceData {
997 type Error = SignatureError;
998
999 fn try_from(device_keys: &DeviceKeys) -> Result<Self, Self::Error> {
1000 let device = 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 device.verify_device_keys(device_keys)?;
1010 Ok(device)
1011 }
1012}
1013
1014impl PartialEq for DeviceData {
1015 fn eq(&self, other: &Self) -> bool {
1016 self.user_id() == other.user_id() && self.device_id() == other.device_id()
1017 }
1018}
1019
1020#[cfg(any(test, feature = "testing"))]
1022#[allow(dead_code)]
1023pub(crate) mod testing {
1024 use serde_json::json;
1025
1026 use crate::{identities::DeviceData, types::DeviceKeys};
1027
1028 pub fn device_keys() -> DeviceKeys {
1030 let device_keys = json!({
1031 "algorithms": vec![
1032 "m.olm.v1.curve25519-aes-sha2",
1033 "m.megolm.v1.aes-sha2"
1034 ],
1035 "device_id": "BNYQQWUMXO",
1036 "user_id": "@example:localhost",
1037 "keys": {
1038 "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1039 "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1040 },
1041 "signatures": {
1042 "@example:localhost": {
1043 "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1044 }
1045 },
1046 "unsigned": {
1047 "device_display_name": "Alice's mobile phone"
1048 }
1049 });
1050
1051 serde_json::from_value(device_keys).unwrap()
1052 }
1053
1054 pub fn get_device() -> DeviceData {
1056 let device_keys = device_keys();
1057 DeviceData::try_from(&device_keys).unwrap()
1058 }
1059}
1060
1061#[cfg(test)]
1062pub(crate) mod tests {
1063 use ruma::{user_id, MilliSecondsSinceUnixEpoch};
1064 use serde_json::json;
1065 use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
1066
1067 use super::testing::{device_keys, get_device};
1068 use crate::{identities::LocalTrust, DeviceData};
1069
1070 #[test]
1071 fn create_a_device() {
1072 let now = MilliSecondsSinceUnixEpoch::now();
1073 let user_id = user_id!("@example:localhost");
1074 let device_id = "BNYQQWUMXO";
1075
1076 let device = get_device();
1077
1078 assert_eq!(user_id, device.user_id());
1079 assert_eq!(device_id, device.device_id());
1080 assert_eq!(device.algorithms().len(), 2);
1081 assert_eq!(LocalTrust::Unset, device.local_trust_state());
1082 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1083 assert_eq!(
1084 device.curve25519_key().unwrap(),
1085 Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1086 .unwrap(),
1087 );
1088 assert_eq!(
1089 device.ed25519_key().unwrap(),
1090 Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1091 );
1092
1093 let then = MilliSecondsSinceUnixEpoch::now();
1094
1095 assert!(device.first_time_seen_ts() >= now);
1096 assert!(device.first_time_seen_ts() <= then);
1097 }
1098
1099 #[test]
1100 fn update_a_device() {
1101 let mut device = get_device();
1102
1103 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1104
1105 let display_name = "Alice's work computer".to_owned();
1106
1107 let mut device_keys = device_keys();
1108 device_keys.unsigned.device_display_name = Some(display_name.clone());
1109 assert!(device.update_device(&device_keys).unwrap());
1110 assert_eq!(&display_name, device.display_name().as_ref().unwrap());
1111
1112 assert!(!device.update_device(&device_keys).unwrap());
1114 }
1115
1116 #[test]
1117 #[allow(clippy::redundant_clone)]
1118 fn delete_a_device() {
1119 let device = get_device();
1120 assert!(!device.is_deleted());
1121
1122 let device_clone = device.clone();
1123
1124 device.mark_as_deleted();
1125 assert!(device.is_deleted());
1126 assert!(device_clone.is_deleted());
1127 }
1128
1129 #[test]
1130 fn deserialize_device() {
1131 let user_id = user_id!("@example:localhost");
1132 let device_id = "BNYQQWUMXO";
1133
1134 let device = json!({
1135 "inner": {
1136 "user_id": user_id,
1137 "device_id": device_id,
1138 "algorithms": ["m.olm.v1.curve25519-aes-sha2","m.megolm.v1.aes-sha2"],
1139 "keys": {
1140 "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1141 "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1142 },
1143 "signatures": {
1144 "@example:localhost": {
1145 "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1146 }
1147 },
1148 "unsigned": {
1149 "device_display_name": "Alice's mobile phone"
1150 }
1151 },
1152 "deleted": false,
1153 "trust_state": "Verified",
1154 "withheld_code_sent": false,
1155 "first_time_seen_ts": 1696931068314u64
1156 });
1157
1158 let device: DeviceData =
1159 serde_json::from_value(device).expect("We should be able to deserialize our device");
1160
1161 assert_eq!(user_id, device.user_id());
1162 assert_eq!(device_id, device.device_id());
1163 assert_eq!(device.algorithms().len(), 2);
1164 assert_eq!(LocalTrust::Verified, device.local_trust_state());
1165 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1166 assert_eq!(
1167 device.curve25519_key().unwrap(),
1168 Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1169 .unwrap(),
1170 );
1171 assert_eq!(
1172 device.ed25519_key().unwrap(),
1173 Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1174 );
1175 }
1176}