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::{InboundGroupSession, OutboundGroupSession, Session, ShareInfo, VerifyJson},
44 session_manager::{withheld_code_for_device_for_share_strategy, CollectStrategy},
45 store::{
46 caches::SequenceNumber,
47 types::{Changes, DeviceChanges},
48 CryptoStoreWrapper, Result as StoreResult,
49 },
50 types::{
51 events::{
52 forwarded_room_key::ForwardedRoomKeyContent,
53 room::encrypted::ToDeviceEncryptedEventContent, EventType,
54 },
55 requests::{OutgoingVerificationRequest, ToDeviceRequest},
56 DeviceKey, DeviceKeys, EventEncryptionAlgorithm, Signatures, SignedKey,
57 },
58 verification::VerificationMachine,
59 Account, Sas, VerificationRequest,
60};
61
62pub enum MaybeEncryptedRoomKey {
63 Encrypted {
64 used_session: Box<Session>,
66 share_info: Box<ShareInfo>,
68 message: Raw<AnyToDeviceEventContent>,
69 },
70 MissingSession,
73}
74
75#[derive(Clone, Serialize, Deserialize)]
77pub struct DeviceData {
78 #[serde(alias = "inner")]
79 pub(crate) device_keys: Arc<DeviceKeys>,
80 #[serde(
81 serialize_with = "atomic_bool_serializer",
82 deserialize_with = "atomic_bool_deserializer"
83 )]
84 deleted: Arc<AtomicBool>,
85 trust_state: Arc<RwLock<LocalTrust>>,
86 #[serde(
89 default,
90 serialize_with = "atomic_bool_serializer",
91 deserialize_with = "atomic_bool_deserializer"
92 )]
93 withheld_code_sent: Arc<AtomicBool>,
94 #[serde(default = "default_timestamp")]
97 first_time_seen_ts: MilliSecondsSinceUnixEpoch,
98 #[serde(default)]
101 pub(crate) olm_wedging_index: SequenceNumber,
102}
103
104fn default_timestamp() -> MilliSecondsSinceUnixEpoch {
105 MilliSecondsSinceUnixEpoch(UInt::default())
106}
107
108#[cfg(not(tarpaulin_include))]
109impl std::fmt::Debug for DeviceData {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.debug_struct("DeviceData")
112 .field("user_id", &self.user_id())
113 .field("device_id", &self.device_id())
114 .field("display_name", &self.display_name())
115 .field("keys", self.keys())
116 .field("deleted", &self.deleted.load(Ordering::SeqCst))
117 .field("trust_state", &self.trust_state)
118 .field("withheld_code_sent", &self.withheld_code_sent)
119 .finish()
120 }
121}
122
123#[derive(Clone)]
125pub struct Device {
126 pub(crate) inner: DeviceData,
127 pub(crate) verification_machine: VerificationMachine,
128 pub(crate) own_identity: Option<OwnUserIdentityData>,
129 pub(crate) device_owner_identity: Option<UserIdentityData>,
130}
131
132#[cfg(not(tarpaulin_include))]
133impl std::fmt::Debug for Device {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.debug_struct("Device").field("device", &self.inner).finish()
136 }
137}
138
139impl Deref for Device {
140 type Target = DeviceData;
141
142 fn deref(&self) -> &Self::Target {
143 &self.inner
144 }
145}
146
147impl Device {
148 pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
158 let (sas, request) = self.verification_machine.start_sas(self.inner.clone()).await?;
159
160 if let OutgoingVerificationRequest::ToDevice(r) = request {
161 Ok((sas, r))
162 } else {
163 panic!("Invalid verification request type");
164 }
165 }
166
167 pub fn is_our_own_device(&self) -> bool {
169 let own_ed25519_key = self.verification_machine.store.account.identity_keys.ed25519;
170 let own_curve25519_key = self.verification_machine.store.account.identity_keys.curve25519;
171
172 self.user_id() == self.verification_machine.own_user_id()
173 && self.device_id() == self.verification_machine.own_device_id()
174 && self.ed25519_key().is_some_and(|k| k == own_ed25519_key)
175 && self.curve25519_key().is_some_and(|k| k == own_curve25519_key)
176 }
177
178 pub fn is_owner_of_session(
184 &self,
185 session: &InboundGroupSession,
186 ) -> Result<bool, MismatchedIdentityKeysError> {
187 if session.has_been_imported() {
188 Ok(false)
208 } else if let Some(key) =
209 session.signing_keys().get(&DeviceKeyAlgorithm::Ed25519).and_then(|k| k.ed25519())
210 {
211 let ed25519_comparison = self.ed25519_key().map(|k| k == key);
270 let curve25519_comparison = self.curve25519_key().map(|k| k == session.sender_key());
271
272 match (ed25519_comparison, curve25519_comparison) {
273 (_, Some(false)) | (Some(false), _) => Err(MismatchedIdentityKeysError {
276 key_ed25519: key.into(),
277 device_ed25519: self.ed25519_key().map(Into::into),
278 key_curve25519: session.sender_key().into(),
279 device_curve25519: self.curve25519_key().map(Into::into),
280 }),
281 (Some(true), Some(true)) => Ok(true),
283 _ => Ok(false),
286 }
287 } else {
288 Ok(false)
289 }
290 }
291
292 pub fn is_cross_signed_by_owner(&self) -> bool {
294 self.device_owner_identity
295 .as_ref()
296 .is_some_and(|owner_identity| self.inner.is_cross_signed_by_owner(owner_identity))
297 }
298
299 pub fn is_device_owner_verified(&self) -> bool {
301 self.device_owner_identity.as_ref().is_some_and(|id| match id {
302 UserIdentityData::Own(own_identity) => own_identity.is_verified(),
303 UserIdentityData::Other(other_identity) => {
304 self.own_identity.as_ref().is_some_and(|oi| oi.is_identity_verified(other_identity))
305 }
306 })
307 }
308
309 pub fn request_verification(&self) -> (VerificationRequest, OutgoingVerificationRequest) {
314 self.request_verification_helper(None)
315 }
316
317 pub fn request_verification_with_methods(
326 &self,
327 methods: Vec<VerificationMethod>,
328 ) -> (VerificationRequest, OutgoingVerificationRequest) {
329 self.request_verification_helper(Some(methods))
330 }
331
332 fn request_verification_helper(
333 &self,
334 methods: Option<Vec<VerificationMethod>>,
335 ) -> (VerificationRequest, OutgoingVerificationRequest) {
336 self.verification_machine.request_to_device_verification(
337 self.user_id(),
338 vec![self.device_id().to_owned()],
339 methods,
340 )
341 }
342
343 pub(crate) async fn get_most_recent_session(&self) -> OlmResult<Option<Session>> {
345 self.inner.get_most_recent_session(self.verification_machine.store.inner()).await
346 }
347
348 pub fn is_verified(&self) -> bool {
356 self.inner.is_verified(&self.own_identity, &self.device_owner_identity)
357 }
358
359 pub fn is_cross_signing_trusted(&self) -> bool {
361 self.inner.is_cross_signing_trusted(&self.own_identity, &self.device_owner_identity)
362 }
363
364 pub async fn verify(&self) -> Result<SignatureUploadRequest, SignatureError> {
378 if self.user_id() == self.verification_machine.own_user_id() {
379 Ok(self
380 .verification_machine
381 .store
382 .private_identity
383 .lock()
384 .await
385 .sign_device(&self.inner)
386 .await?)
387 } else {
388 Err(SignatureError::UserIdMismatch)
389 }
390 }
391
392 pub async fn set_local_trust(&self, trust_state: LocalTrust) -> StoreResult<()> {
401 self.inner.set_trust_state(trust_state);
402
403 let changes = Changes {
404 devices: DeviceChanges { changed: vec![self.inner.clone()], ..Default::default() },
405 ..Default::default()
406 };
407
408 self.verification_machine.store.save_changes(changes).await
409 }
410
411 pub(crate) async fn encrypt(
417 &self,
418 event_type: &str,
419 content: impl Serialize,
420 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
421 self.inner.encrypt(self.verification_machine.store.inner(), event_type, content).await
422 }
423
424 pub async fn encrypt_room_key_for_forwarding(
427 &self,
428 session: InboundGroupSession,
429 message_index: Option<u32>,
430 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
431 let content: ForwardedRoomKeyContent = {
432 let export = if let Some(index) = message_index {
433 session.export_at_index(index).await
434 } else {
435 session.export().await
436 };
437
438 export.try_into()?
439 };
440
441 let event_type = content.event_type().to_owned();
442
443 self.encrypt(&event_type, content).await
444 }
445
446 pub async fn encrypt_event_raw(
473 &self,
474 event_type: &str,
475 content: &Value,
476 share_strategy: CollectStrategy,
477 ) -> OlmResult<Raw<ToDeviceEncryptedEventContent>> {
478 if let Some(withheld_code) = withheld_code_for_device_for_share_strategy(
479 &self.inner,
480 share_strategy,
481 &self.own_identity,
482 &self.device_owner_identity,
483 )
484 .await?
485 {
486 return Err(OlmError::Withheld(withheld_code));
487 }
488
489 let (used_session, raw_encrypted) = self.encrypt(event_type, content).await?;
490
491 self.verification_machine
493 .store
494 .save_changes(Changes { sessions: vec![used_session], ..Default::default() })
495 .await?;
496
497 Ok(raw_encrypted)
498 }
499
500 pub fn is_dehydrated(&self) -> bool {
502 self.inner.is_dehydrated()
503 }
504}
505
506#[derive(Debug)]
508pub struct UserDevices {
509 pub(crate) inner: HashMap<OwnedDeviceId, DeviceData>,
510 pub(crate) verification_machine: VerificationMachine,
511 pub(crate) own_identity: Option<OwnUserIdentityData>,
512 pub(crate) device_owner_identity: Option<UserIdentityData>,
513}
514
515impl UserDevices {
516 pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
518 self.inner.get(device_id).map(|d| Device {
519 inner: d.clone(),
520 verification_machine: self.verification_machine.clone(),
521 own_identity: self.own_identity.clone(),
522 device_owner_identity: self.device_owner_identity.clone(),
523 })
524 }
525
526 fn own_user_id(&self) -> &UserId {
527 self.verification_machine.own_user_id()
528 }
529
530 fn own_device_id(&self) -> &DeviceId {
531 self.verification_machine.own_device_id()
532 }
533
534 pub fn is_any_verified(&self) -> bool {
540 self.inner
541 .values()
542 .filter(|d| {
543 !(d.user_id() == self.own_user_id() && d.device_id() == self.own_device_id())
544 })
545 .any(|d| d.is_verified(&self.own_identity, &self.device_owner_identity))
546 }
547
548 pub fn keys(&self) -> impl Iterator<Item = &DeviceId> {
550 self.inner.keys().map(Deref::deref)
551 }
552
553 pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
555 self.inner.values().map(move |d| Device {
556 inner: d.clone(),
557 verification_machine: self.verification_machine.clone(),
558 own_identity: self.own_identity.clone(),
559 device_owner_identity: self.device_owner_identity.clone(),
560 })
561 }
562}
563
564#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
566#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
567pub enum LocalTrust {
568 Verified = 0,
570 BlackListed = 1,
572 Ignored = 2,
574 Unset = 3,
576}
577
578impl From<i64> for LocalTrust {
579 fn from(state: i64) -> Self {
580 match state {
581 0 => LocalTrust::Verified,
582 1 => LocalTrust::BlackListed,
583 2 => LocalTrust::Ignored,
584 3 => LocalTrust::Unset,
585 _ => LocalTrust::Unset,
586 }
587 }
588}
589
590impl DeviceData {
591 pub fn new(device_keys: DeviceKeys, trust_state: LocalTrust) -> Self {
595 Self {
596 device_keys: device_keys.into(),
597 trust_state: Arc::new(RwLock::new(trust_state)),
598 deleted: Arc::new(AtomicBool::new(false)),
599 withheld_code_sent: Arc::new(AtomicBool::new(false)),
600 first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
601 olm_wedging_index: Default::default(),
602 }
603 }
604
605 pub fn user_id(&self) -> &UserId {
607 &self.device_keys.user_id
608 }
609
610 pub fn device_id(&self) -> &DeviceId {
612 &self.device_keys.device_id
613 }
614
615 pub fn display_name(&self) -> Option<&str> {
617 self.device_keys.unsigned.device_display_name.as_deref()
618 }
619
620 pub fn get_key(&self, algorithm: DeviceKeyAlgorithm) -> Option<&DeviceKey> {
622 self.device_keys.get_key(algorithm)
623 }
624
625 pub fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
627 self.device_keys.curve25519_key()
628 }
629
630 pub fn ed25519_key(&self) -> Option<Ed25519PublicKey> {
632 self.device_keys.ed25519_key()
633 }
634
635 pub fn keys(&self) -> &BTreeMap<OwnedDeviceKeyId, DeviceKey> {
637 &self.device_keys.keys
638 }
639
640 pub fn signatures(&self) -> &Signatures {
642 &self.device_keys.signatures
643 }
644
645 pub fn local_trust_state(&self) -> LocalTrust {
647 *self.trust_state.read()
648 }
649
650 pub fn is_locally_trusted(&self) -> bool {
652 self.local_trust_state() == LocalTrust::Verified
653 }
654
655 pub fn is_blacklisted(&self) -> bool {
659 self.local_trust_state() == LocalTrust::BlackListed
660 }
661
662 pub(crate) fn set_trust_state(&self, state: LocalTrust) {
667 *self.trust_state.write() = state;
668 }
669
670 pub(crate) fn mark_withheld_code_as_sent(&self) {
671 self.withheld_code_sent.store(true, Ordering::Relaxed)
672 }
673
674 pub fn was_withheld_code_sent(&self) -> bool {
677 self.withheld_code_sent.load(Ordering::Relaxed)
678 }
679
680 pub fn algorithms(&self) -> &[EventEncryptionAlgorithm] {
682 &self.device_keys.algorithms
683 }
684
685 pub fn supports_olm(&self) -> bool {
687 #[cfg(feature = "experimental-algorithms")]
688 {
689 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
690 || self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
691 }
692
693 #[cfg(not(feature = "experimental-algorithms"))]
694 {
695 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
696 }
697 }
698
699 pub(crate) async fn get_most_recent_session(
702 &self,
703 store: &CryptoStoreWrapper,
704 ) -> OlmResult<Option<Session>> {
705 if let Some(sender_key) = self.curve25519_key() {
706 if let Some(sessions) = store.get_sessions(&sender_key.to_base64()).await? {
707 let mut sessions = sessions.lock().await;
708 sessions.sort_by_key(|s| s.creation_time);
709
710 Ok(sessions.last().cloned())
711 } else {
712 Ok(None)
713 }
714 } else {
715 Ok(None)
716 }
717 }
718
719 #[cfg(feature = "experimental-algorithms")]
722 pub fn supports_olm_v2(&self) -> bool {
723 self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
724 }
725
726 pub fn olm_session_config(&self) -> SessionConfig {
728 #[cfg(feature = "experimental-algorithms")]
729 if self.supports_olm_v2() {
730 SessionConfig::version_2()
731 } else {
732 SessionConfig::version_1()
733 }
734
735 #[cfg(not(feature = "experimental-algorithms"))]
736 SessionConfig::version_1()
737 }
738
739 pub fn is_deleted(&self) -> bool {
741 self.deleted.load(Ordering::Relaxed)
742 }
743
744 pub(crate) fn is_verified(
745 &self,
746 own_identity: &Option<OwnUserIdentityData>,
747 device_owner: &Option<UserIdentityData>,
748 ) -> bool {
749 self.is_locally_trusted() || self.is_cross_signing_trusted(own_identity, device_owner)
750 }
751
752 pub(crate) fn is_cross_signing_trusted(
753 &self,
754 own_identity: &Option<OwnUserIdentityData>,
755 device_owner: &Option<UserIdentityData>,
756 ) -> bool {
757 own_identity.as_ref().zip(device_owner.as_ref()).is_some_and(
758 |(own_identity, device_identity)| {
759 match device_identity {
760 UserIdentityData::Own(_) => {
761 own_identity.is_verified() && own_identity.is_device_signed(self)
762 }
763
764 UserIdentityData::Other(device_identity) => {
768 own_identity.is_identity_verified(device_identity)
769 && device_identity.is_device_signed(self)
770 }
771 }
772 },
773 )
774 }
775
776 pub(crate) fn is_cross_signed_by_owner(
777 &self,
778 device_owner_identity: &UserIdentityData,
779 ) -> bool {
780 match device_owner_identity {
781 UserIdentityData::Own(identity) => identity.is_device_signed(self),
784 UserIdentityData::Other(device_identity) => device_identity.is_device_signed(self),
787 }
788 }
789
790 #[instrument(
808 skip_all,
809 fields(
810 recipient = ?self.user_id(),
811 recipient_device = ?self.device_id(),
812 recipient_key = ?self.curve25519_key(),
813 event_type,
814 message_id,
815 ))
816 ]
817 pub(crate) async fn encrypt(
818 &self,
819 store: &CryptoStoreWrapper,
820 event_type: &str,
821 content: impl Serialize,
822 ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
823 #[cfg(not(target_family = "wasm"))]
824 let message_id = ulid::Ulid::new().to_string();
825 #[cfg(target_family = "wasm")]
826 let message_id = ruma::TransactionId::new().to_string();
827
828 tracing::Span::current().record("message_id", &message_id);
829
830 let session = self.get_most_recent_session(store).await?;
831
832 if let Some(mut session) = session {
833 let message = session.encrypt(self, event_type, content, Some(message_id)).await?;
834 Ok((session, message))
835 } else {
836 trace!("Trying to encrypt an event for a device, but no Olm session is found.");
837 Err(OlmError::MissingSession)
838 }
839 }
840
841 pub(crate) async fn maybe_encrypt_room_key(
842 &self,
843 store: &CryptoStoreWrapper,
844 session: OutboundGroupSession,
845 ) -> OlmResult<MaybeEncryptedRoomKey> {
846 let content = session.as_content().await;
847 let message_index = session.message_index().await;
848 let event_type = content.event_type().to_owned();
849
850 match self.encrypt(store, &event_type, content).await {
851 Ok((session, encrypted)) => Ok(MaybeEncryptedRoomKey::Encrypted {
852 share_info: Box::new(ShareInfo::new_shared(
853 session.sender_key().to_owned(),
854 message_index,
855 self.olm_wedging_index,
856 )),
857 used_session: Box::new(session),
858 message: encrypted.cast(),
859 }),
860
861 Err(OlmError::MissingSession) => Ok(MaybeEncryptedRoomKey::MissingSession),
862 Err(e) => Err(e),
863 }
864 }
865
866 pub(crate) fn update_device(
870 &mut self,
871 device_keys: &DeviceKeys,
872 ) -> Result<bool, SignatureError> {
873 device_keys.check_self_signature()?;
874
875 if self.user_id() != device_keys.user_id || self.device_id() != device_keys.device_id {
876 Err(SignatureError::UserIdMismatch)
877 } else if self.ed25519_key() != device_keys.ed25519_key() {
878 Err(SignatureError::SigningKeyChanged(
879 self.ed25519_key().map(Box::new),
880 device_keys.ed25519_key().map(Box::new),
881 ))
882 } else if self.device_keys.as_ref() != device_keys {
883 trace!(
884 user_id = ?self.user_id(),
885 device_id = ?self.device_id(),
886 keys = ?self.keys(),
887 "Updated a device",
888 );
889
890 self.device_keys = device_keys.clone().into();
891
892 Ok(true)
893 } else {
894 Ok(false)
896 }
897 }
898
899 pub fn as_device_keys(&self) -> &DeviceKeys {
901 &self.device_keys
902 }
903
904 pub(crate) fn has_signed_raw(
914 &self,
915 signatures: &Signatures,
916 canonical_json: &str,
917 ) -> Result<(), SignatureError> {
918 let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
919 let user_id = self.user_id();
920 let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id());
921
922 key.verify_canonicalized_json(user_id, key_id, signatures, canonical_json)
923 }
924
925 pub(crate) fn verify_one_time_key(
926 &self,
927 one_time_key: &SignedKey,
928 ) -> Result<(), SignatureError> {
929 self.device_keys.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 device_keys.check_self_signature()?;
984 Ok(Self {
985 device_keys: device_keys.clone().into(),
986 deleted: Arc::new(AtomicBool::new(false)),
987 trust_state: Arc::new(RwLock::new(LocalTrust::Unset)),
988 withheld_code_sent: Arc::new(AtomicBool::new(false)),
989 first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
990 olm_wedging_index: Default::default(),
991 })
992 }
993}
994
995impl PartialEq for DeviceData {
996 fn eq(&self, other: &Self) -> bool {
997 self.user_id() == other.user_id() && self.device_id() == other.device_id()
998 }
999}
1000
1001#[cfg(any(test, feature = "testing"))]
1003#[allow(dead_code)]
1004pub(crate) mod testing {
1005 use serde_json::json;
1006
1007 use crate::{identities::DeviceData, types::DeviceKeys};
1008
1009 pub fn device_keys() -> DeviceKeys {
1011 let device_keys = json!({
1012 "algorithms": vec![
1013 "m.olm.v1.curve25519-aes-sha2",
1014 "m.megolm.v1.aes-sha2"
1015 ],
1016 "device_id": "BNYQQWUMXO",
1017 "user_id": "@example:localhost",
1018 "keys": {
1019 "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1020 "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1021 },
1022 "signatures": {
1023 "@example:localhost": {
1024 "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1025 }
1026 },
1027 "unsigned": {
1028 "device_display_name": "Alice's mobile phone"
1029 }
1030 });
1031
1032 serde_json::from_value(device_keys).unwrap()
1033 }
1034
1035 pub fn get_device() -> DeviceData {
1037 let device_keys = device_keys();
1038 DeviceData::try_from(&device_keys).unwrap()
1039 }
1040}
1041
1042#[cfg(test)]
1043pub(crate) mod tests {
1044 use ruma::{user_id, MilliSecondsSinceUnixEpoch};
1045 use serde_json::json;
1046 use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
1047
1048 use super::testing::{device_keys, get_device};
1049 use crate::{identities::LocalTrust, DeviceData};
1050
1051 #[test]
1052 fn create_a_device() {
1053 let now = MilliSecondsSinceUnixEpoch::now();
1054 let user_id = user_id!("@example:localhost");
1055 let device_id = "BNYQQWUMXO";
1056
1057 let device = get_device();
1058
1059 assert_eq!(user_id, device.user_id());
1060 assert_eq!(device_id, device.device_id());
1061 assert_eq!(device.algorithms().len(), 2);
1062 assert_eq!(LocalTrust::Unset, device.local_trust_state());
1063 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1064 assert_eq!(
1065 device.curve25519_key().unwrap(),
1066 Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1067 .unwrap(),
1068 );
1069 assert_eq!(
1070 device.ed25519_key().unwrap(),
1071 Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1072 );
1073
1074 let then = MilliSecondsSinceUnixEpoch::now();
1075
1076 assert!(device.first_time_seen_ts() >= now);
1077 assert!(device.first_time_seen_ts() <= then);
1078 }
1079
1080 #[test]
1081 fn update_a_device() {
1082 let mut device = get_device();
1083
1084 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1085
1086 let display_name = "Alice's work computer".to_owned();
1087
1088 let mut device_keys = device_keys();
1089 device_keys.unsigned.device_display_name = Some(display_name.clone());
1090 assert!(device.update_device(&device_keys).unwrap());
1091 assert_eq!(&display_name, device.display_name().as_ref().unwrap());
1092
1093 assert!(!device.update_device(&device_keys).unwrap());
1095 }
1096
1097 #[test]
1098 #[allow(clippy::redundant_clone)]
1099 fn delete_a_device() {
1100 let device = get_device();
1101 assert!(!device.is_deleted());
1102
1103 let device_clone = device.clone();
1104
1105 device.mark_as_deleted();
1106 assert!(device.is_deleted());
1107 assert!(device_clone.is_deleted());
1108 }
1109
1110 #[test]
1111 fn deserialize_device() {
1112 let user_id = user_id!("@example:localhost");
1113 let device_id = "BNYQQWUMXO";
1114
1115 let device = json!({
1116 "inner": {
1117 "user_id": user_id,
1118 "device_id": device_id,
1119 "algorithms": ["m.olm.v1.curve25519-aes-sha2","m.megolm.v1.aes-sha2"],
1120 "keys": {
1121 "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1122 "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1123 },
1124 "signatures": {
1125 "@example:localhost": {
1126 "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1127 }
1128 },
1129 "unsigned": {
1130 "device_display_name": "Alice's mobile phone"
1131 }
1132 },
1133 "deleted": false,
1134 "trust_state": "Verified",
1135 "withheld_code_sent": false,
1136 "first_time_seen_ts": 1696931068314u64
1137 });
1138
1139 let device: DeviceData =
1140 serde_json::from_value(device).expect("We should be able to deserialize our device");
1141
1142 assert_eq!(user_id, device.user_id());
1143 assert_eq!(device_id, device.device_id());
1144 assert_eq!(device.algorithms().len(), 2);
1145 assert_eq!(LocalTrust::Verified, device.local_trust_state());
1146 assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1147 assert_eq!(
1148 device.curve25519_key().unwrap(),
1149 Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1150 .unwrap(),
1151 );
1152 assert_eq!(
1153 device.ed25519_key().unwrap(),
1154 Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1155 );
1156 }
1157}