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::{debug, instrument, trace, warn};
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::{EventError, 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 (event_type, content) = {
429 let export = if let Some(index) = message_index {
430 session.export_at_index(index).await
431 } else {
432 session.export().await
433 };
434 let content: ForwardedRoomKeyContent = export.try_into()?;
435
436 (content.event_type(), content)
437 };
438
439 self.encrypt(event_type, content).await
440 }
441
442 pub async fn encrypt_event_raw(
467 &self,
468 event_type: &str,
469 content: &Value,
470 ) -> OlmResult<Raw<ToDeviceEncryptedEventContent>> {
471 let (used_session, raw_encrypted) = self.encrypt(event_type, content).await?;
472
473 self.verification_machine
475 .store
476 .save_changes(Changes { sessions: vec![used_session], ..Default::default() })
477 .await?;
478
479 Ok(raw_encrypted)
480 }
481
482 pub fn is_dehydrated(&self) -> bool {
484 self.inner.is_dehydrated()
485 }
486}
487
488#[derive(Debug)]
490pub struct UserDevices {
491 pub(crate) inner: HashMap<OwnedDeviceId, DeviceData>,
492 pub(crate) verification_machine: VerificationMachine,
493 pub(crate) own_identity: Option<OwnUserIdentityData>,
494 pub(crate) device_owner_identity: Option<UserIdentityData>,
495}
496
497impl UserDevices {
498 pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
500 self.inner.get(device_id).map(|d| Device {
501 inner: d.clone(),
502 verification_machine: self.verification_machine.clone(),
503 own_identity: self.own_identity.clone(),
504 device_owner_identity: self.device_owner_identity.clone(),
505 })
506 }
507
508 fn own_user_id(&self) -> &UserId {
509 self.verification_machine.own_user_id()
510 }
511
512 fn own_device_id(&self) -> &DeviceId {
513 self.verification_machine.own_device_id()
514 }
515
516 pub fn is_any_verified(&self) -> bool {
522 self.inner
523 .values()
524 .filter(|d| {
525 !(d.user_id() == self.own_user_id() && d.device_id() == self.own_device_id())
526 })
527 .any(|d| d.is_verified(&self.own_identity, &self.device_owner_identity))
528 }
529
530 pub fn keys(&self) -> impl Iterator<Item = &DeviceId> {
532 self.inner.keys().map(Deref::deref)
533 }
534
535 pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
537 self.inner.values().map(move |d| Device {
538 inner: d.clone(),
539 verification_machine: self.verification_machine.clone(),
540 own_identity: self.own_identity.clone(),
541 device_owner_identity: self.device_owner_identity.clone(),
542 })
543 }
544}
545
546#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
548#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
549pub enum LocalTrust {
550 Verified = 0,
552 BlackListed = 1,
554 Ignored = 2,
556 Unset = 3,
558}
559
560impl From<i64> for LocalTrust {
561 fn from(state: i64) -> Self {
562 match state {
563 0 => LocalTrust::Verified,
564 1 => LocalTrust::BlackListed,
565 2 => LocalTrust::Ignored,
566 3 => LocalTrust::Unset,
567 _ => LocalTrust::Unset,
568 }
569 }
570}
571
572impl DeviceData {
573 pub fn new(device_keys: DeviceKeys, trust_state: LocalTrust) -> Self {
577 Self {
578 device_keys: device_keys.into(),
579 trust_state: Arc::new(RwLock::new(trust_state)),
580 deleted: Arc::new(AtomicBool::new(false)),
581 withheld_code_sent: Arc::new(AtomicBool::new(false)),
582 first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
583 olm_wedging_index: Default::default(),
584 }
585 }
586
587 pub fn user_id(&self) -> &UserId {
589 &self.device_keys.user_id
590 }
591
592 pub fn device_id(&self) -> &DeviceId {
594 &self.device_keys.device_id
595 }
596
597 pub fn display_name(&self) -> Option<&str> {
599 self.device_keys.unsigned.device_display_name.as_deref()
600 }
601
602 pub fn get_key(&self, algorithm: DeviceKeyAlgorithm) -> Option<&DeviceKey> {
604 self.device_keys.get_key(algorithm)
605 }
606
607 pub fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
609 self.device_keys.curve25519_key()
610 }
611
612 pub fn ed25519_key(&self) -> Option<Ed25519PublicKey> {
614 self.device_keys.ed25519_key()
615 }
616
617 pub fn keys(&self) -> &BTreeMap<OwnedDeviceKeyId, DeviceKey> {
619 &self.device_keys.keys
620 }
621
622 pub fn signatures(&self) -> &Signatures {
624 &self.device_keys.signatures
625 }
626
627 pub fn local_trust_state(&self) -> LocalTrust {
629 *self.trust_state.read()
630 }
631
632 pub fn is_locally_trusted(&self) -> bool {
634 self.local_trust_state() == LocalTrust::Verified
635 }
636
637 pub fn is_blacklisted(&self) -> bool {
641 self.local_trust_state() == LocalTrust::BlackListed
642 }
643
644 pub(crate) fn set_trust_state(&self, state: LocalTrust) {
649 *self.trust_state.write() = state;
650 }
651
652 pub(crate) fn mark_withheld_code_as_sent(&self) {
653 self.withheld_code_sent.store(true, Ordering::Relaxed)
654 }
655
656 pub fn was_withheld_code_sent(&self) -> bool {
659 self.withheld_code_sent.load(Ordering::Relaxed)
660 }
661
662 pub fn algorithms(&self) -> &[EventEncryptionAlgorithm] {
664 &self.device_keys.algorithms
665 }
666
667 pub fn supports_olm(&self) -> bool {
669 #[cfg(feature = "experimental-algorithms")]
670 {
671 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
672 || self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
673 }
674
675 #[cfg(not(feature = "experimental-algorithms"))]
676 {
677 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
678 }
679 }
680
681 pub(crate) async fn get_most_recent_session(
684 &self,
685 store: &CryptoStoreWrapper,
686 ) -> OlmResult<Option<Session>> {
687 if let Some(sender_key) = self.curve25519_key() {
688 if let Some(sessions) = store.get_sessions(&sender_key.to_base64()).await? {
689 let mut sessions = sessions.lock().await;
690 sessions.sort_by_key(|s| s.creation_time);
691
692 Ok(sessions.last().cloned())
693 } else {
694 Ok(None)
695 }
696 } else {
697 warn!(
698 "Trying to find an Olm session of a device, but the device doesn't have a \
699 Curve25519 key",
700 );
701
702 Err(EventError::MissingSenderKey.into())
703 }
704 }
705
706 #[cfg(feature = "experimental-algorithms")]
709 pub fn supports_olm_v2(&self) -> bool {
710 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
711 }
712
713 pub fn olm_session_config(&self) -> SessionConfig {
715 #[cfg(feature = "experimental-algorithms")]
716 if self.supports_olm_v2() {
717 SessionConfig::version_2()
718 } else {
719 SessionConfig::version_1()
720 }
721
722 #[cfg(not(feature = "experimental-algorithms"))]
723 SessionConfig::version_1()
724 }
725
726 pub fn is_deleted(&self) -> bool {
728 self.deleted.load(Ordering::Relaxed)
729 }
730
731 pub(crate) fn is_verified(
732 &self,
733 own_identity: &Option<OwnUserIdentityData>,
734 device_owner: &Option<UserIdentityData>,
735 ) -> bool {
736 self.is_locally_trusted() || self.is_cross_signing_trusted(own_identity, device_owner)
737 }
738
739 pub(crate) fn is_cross_signing_trusted(
740 &self,
741 own_identity: &Option<OwnUserIdentityData>,
742 device_owner: &Option<UserIdentityData>,
743 ) -> bool {
744 own_identity.as_ref().zip(device_owner.as_ref()).is_some_and(
745 |(own_identity, device_identity)| {
746 match device_identity {
747 UserIdentityData::Own(_) => {
748 own_identity.is_verified() && own_identity.is_device_signed(self)
749 }
750
751 UserIdentityData::Other(device_identity) => {
755 own_identity.is_identity_verified(device_identity)
756 && device_identity.is_device_signed(self)
757 }
758 }
759 },
760 )
761 }
762
763 pub(crate) fn is_cross_signed_by_owner(
764 &self,
765 device_owner_identity: &UserIdentityData,
766 ) -> bool {
767 match device_owner_identity {
768 UserIdentityData::Own(identity) => identity.is_device_signed(self),
771 UserIdentityData::Other(device_identity) => device_identity.is_device_signed(self),
774 }
775 }
776
777 #[instrument(
795 skip_all,
796 fields(
797 recipient = ?self.user_id(),
798 recipient_device = ?self.device_id(),
799 recipient_key = ?self.curve25519_key(),
800 event_type,
801 message_id,
802 ))
803 ]
804 pub(crate) async fn encrypt(
805 &self,
806 store: &CryptoStoreWrapper,
807 event_type: &str,
808 content: impl Serialize,
809 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
810 #[cfg(not(target_arch = "wasm32"))]
811 let message_id = ulid::Ulid::new().to_string();
812 #[cfg(target_arch = "wasm32")]
813 let message_id = ruma::TransactionId::new().to_string();
814
815 tracing::Span::current().record("message_id", &message_id);
816
817 let session = self.get_most_recent_session(store).await?;
818
819 if let Some(mut session) = session {
820 let message = session.encrypt(self, event_type, content, Some(message_id)).await?;
821 Ok((session, message))
822 } else {
823 trace!("Trying to encrypt an event for a device, but no Olm session is found.");
824 Err(OlmError::MissingSession)
825 }
826 }
827
828 pub(crate) async fn maybe_encrypt_room_key(
829 &self,
830 store: &CryptoStoreWrapper,
831 session: OutboundGroupSession,
832 ) -> OlmResult<MaybeEncryptedRoomKey> {
833 let content = session.as_content().await;
834 let message_index = session.message_index().await;
835 let event_type = content.event_type();
836
837 match self.encrypt(store, event_type, content).await {
838 Ok((session, encrypted)) => Ok(MaybeEncryptedRoomKey::Encrypted {
839 share_info: ShareInfo::new_shared(
840 session.sender_key().to_owned(),
841 message_index,
842 self.olm_wedging_index,
843 ),
844 used_session: session,
845 message: encrypted.cast(),
846 }),
847
848 Err(OlmError::MissingSession | OlmError::EventError(EventError::MissingSenderKey)) => {
849 Ok(MaybeEncryptedRoomKey::Withheld { code: WithheldCode::NoOlm })
850 }
851 Err(e) => Err(e),
852 }
853 }
854
855 pub(crate) fn update_device(
859 &mut self,
860 device_keys: &DeviceKeys,
861 ) -> Result<bool, SignatureError> {
862 self.verify_device_keys(device_keys)?;
863
864 if self.user_id() != device_keys.user_id || self.device_id() != device_keys.device_id {
865 Err(SignatureError::UserIdMismatch)
866 } else if self.ed25519_key() != device_keys.ed25519_key() {
867 Err(SignatureError::SigningKeyChanged(
868 self.ed25519_key().map(Box::new),
869 device_keys.ed25519_key().map(Box::new),
870 ))
871 } else if self.device_keys.as_ref() != device_keys {
872 debug!(
873 user_id = ?self.user_id(),
874 device_id = ?self.device_id(),
875 keys = ?self.keys(),
876 "Updated a device",
877 );
878
879 self.device_keys = device_keys.clone().into();
880
881 Ok(true)
882 } else {
883 Ok(false)
885 }
886 }
887
888 pub fn as_device_keys(&self) -> &DeviceKeys {
890 &self.device_keys
891 }
892
893 pub(crate) fn has_signed_raw(
903 &self,
904 signatures: &Signatures,
905 canonical_json: &str,
906 ) -> Result<(), SignatureError> {
907 let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
908 let user_id = self.user_id();
909 let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id());
910
911 key.verify_canonicalized_json(user_id, key_id, signatures, canonical_json)
912 }
913
914 fn has_signed(&self, signed_object: &impl SignedJsonObject) -> Result<(), SignatureError> {
915 let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
916 let user_id = self.user_id();
917 let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id());
918
919 key.verify_json(user_id, key_id, signed_object)
920 }
921
922 pub(crate) fn verify_device_keys(
923 &self,
924 device_keys: &DeviceKeys,
925 ) -> Result<(), SignatureError> {
926 self.has_signed(device_keys)
927 }
928
929 pub(crate) fn verify_one_time_key(
930 &self,
931 one_time_key: &SignedKey,
932 ) -> Result<(), SignatureError> {
933 self.has_signed(one_time_key)
934 }
935
936 pub(crate) fn mark_as_deleted(&self) {
938 self.deleted.store(true, Ordering::Relaxed);
939 }
940
941 #[cfg(any(test, feature = "testing"))]
942 #[allow(dead_code)]
943 pub async fn from_machine_test_helper(
945 machine: &OlmMachine,
946 ) -> Result<DeviceData, crate::CryptoStoreError> {
947 Ok(DeviceData::from_account(&*machine.store().cache().await?.account().await?))
948 }
949
950 pub fn from_account(account: &Account) -> DeviceData {
963 let device_keys = account.device_keys();
964 let mut device = DeviceData::try_from(&device_keys)
965 .expect("Creating a device from our own account should always succeed");
966 device.first_time_seen_ts = account.creation_local_time();
967
968 device
969 }
970
971 pub fn first_time_seen_ts(&self) -> MilliSecondsSinceUnixEpoch {
974 self.first_time_seen_ts
975 }
976
977 pub fn is_dehydrated(&self) -> bool {
979 self.device_keys.dehydrated.unwrap_or(false)
980 }
981}
982
983impl TryFrom<&DeviceKeys> for DeviceData {
984 type Error = SignatureError;
985
986 fn try_from(device_keys: &DeviceKeys) -> Result<Self, Self::Error> {
987 let device = Self {
988 device_keys: device_keys.clone().into(),
989 deleted: Arc::new(AtomicBool::new(false)),
990 trust_state: Arc::new(RwLock::new(LocalTrust::Unset)),
991 withheld_code_sent: Arc::new(AtomicBool::new(false)),
992 first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
993 olm_wedging_index: Default::default(),
994 };
995
996 device.verify_device_keys(device_keys)?;
997 Ok(device)
998 }
999}
1000
1001impl PartialEq for DeviceData {
1002 fn eq(&self, other: &Self) -> bool {
1003 self.user_id() == other.user_id() && self.device_id() == other.device_id()
1004 }
1005}
1006
1007#[cfg(any(test, feature = "testing"))]
1009#[allow(dead_code)]
1010pub(crate) mod testing {
1011 use serde_json::json;
1012
1013 use crate::{identities::DeviceData, types::DeviceKeys};
1014
1015 pub fn device_keys() -> DeviceKeys {
1017 let device_keys = json!({
1018 "algorithms": vec![
1019 "m.olm.v1.curve25519-aes-sha2",
1020 "m.megolm.v1.aes-sha2"
1021 ],
1022 "device_id": "BNYQQWUMXO",
1023 "user_id": "@example:localhost",
1024 "keys": {
1025 "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1026 "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1027 },
1028 "signatures": {
1029 "@example:localhost": {
1030 "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1031 }
1032 },
1033 "unsigned": {
1034 "device_display_name": "Alice's mobile phone"
1035 }
1036 });
1037
1038 serde_json::from_value(device_keys).unwrap()
1039 }
1040
1041 pub fn get_device() -> DeviceData {
1043 let device_keys = device_keys();
1044 DeviceData::try_from(&device_keys).unwrap()
1045 }
1046}
1047
1048#[cfg(test)]
1049pub(crate) mod tests {
1050 use ruma::{user_id, MilliSecondsSinceUnixEpoch};
1051 use serde_json::json;
1052 use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
1053
1054 use super::testing::{device_keys, get_device};
1055 use crate::{identities::LocalTrust, DeviceData};
1056
1057 #[test]
1058 fn create_a_device() {
1059 let now = MilliSecondsSinceUnixEpoch::now();
1060 let user_id = user_id!("@example:localhost");
1061 let device_id = "BNYQQWUMXO";
1062
1063 let device = get_device();
1064
1065 assert_eq!(user_id, device.user_id());
1066 assert_eq!(device_id, device.device_id());
1067 assert_eq!(device.algorithms().len(), 2);
1068 assert_eq!(LocalTrust::Unset, device.local_trust_state());
1069 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1070 assert_eq!(
1071 device.curve25519_key().unwrap(),
1072 Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1073 .unwrap(),
1074 );
1075 assert_eq!(
1076 device.ed25519_key().unwrap(),
1077 Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1078 );
1079
1080 let then = MilliSecondsSinceUnixEpoch::now();
1081
1082 assert!(device.first_time_seen_ts() >= now);
1083 assert!(device.first_time_seen_ts() <= then);
1084 }
1085
1086 #[test]
1087 fn update_a_device() {
1088 let mut device = get_device();
1089
1090 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1091
1092 let display_name = "Alice's work computer".to_owned();
1093
1094 let mut device_keys = device_keys();
1095 device_keys.unsigned.device_display_name = Some(display_name.clone());
1096 assert!(device.update_device(&device_keys).unwrap());
1097 assert_eq!(&display_name, device.display_name().as_ref().unwrap());
1098
1099 assert!(!device.update_device(&device_keys).unwrap());
1101 }
1102
1103 #[test]
1104 #[allow(clippy::redundant_clone)]
1105 fn delete_a_device() {
1106 let device = get_device();
1107 assert!(!device.is_deleted());
1108
1109 let device_clone = device.clone();
1110
1111 device.mark_as_deleted();
1112 assert!(device.is_deleted());
1113 assert!(device_clone.is_deleted());
1114 }
1115
1116 #[test]
1117 fn deserialize_device() {
1118 let user_id = user_id!("@example:localhost");
1119 let device_id = "BNYQQWUMXO";
1120
1121 let device = json!({
1122 "inner": {
1123 "user_id": user_id,
1124 "device_id": device_id,
1125 "algorithms": ["m.olm.v1.curve25519-aes-sha2","m.megolm.v1.aes-sha2"],
1126 "keys": {
1127 "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1128 "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1129 },
1130 "signatures": {
1131 "@example:localhost": {
1132 "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1133 }
1134 },
1135 "unsigned": {
1136 "device_display_name": "Alice's mobile phone"
1137 }
1138 },
1139 "deleted": false,
1140 "trust_state": "Verified",
1141 "withheld_code_sent": false,
1142 "first_time_seen_ts": 1696931068314u64
1143 });
1144
1145 let device: DeviceData =
1146 serde_json::from_value(device).expect("We should be able to deserialize our device");
1147
1148 assert_eq!(user_id, device.user_id());
1149 assert_eq!(device_id, device.device_id());
1150 assert_eq!(device.algorithms().len(), 2);
1151 assert_eq!(LocalTrust::Verified, device.local_trust_state());
1152 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1153 assert_eq!(
1154 device.curve25519_key().unwrap(),
1155 Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1156 .unwrap(),
1157 );
1158 assert_eq!(
1159 device.ed25519_key().unwrap(),
1160 Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1161 );
1162 }
1163}