1mod helpers;
16mod inner_sas;
17mod sas_state;
18
19use std::sync::Arc;
20
21use as_variant::as_variant;
22use eyeball::{ObservableWriteGuard, SharedObservable};
23use futures_core::Stream;
24use futures_util::StreamExt;
25use inner_sas::InnerSas;
26use ruma::{
27 DeviceId, OwnedEventId, OwnedRoomId, OwnedTransactionId, RoomId, TransactionId, UserId,
28 api::client::keys::upload_signatures::v3::Request as SignatureUploadRequest,
29 events::{
30 AnyMessageLikeEventContent, AnyToDeviceEventContent,
31 key::verification::{ShortAuthenticationString, cancel::CancelCode, start::SasV1Content},
32 },
33};
34pub use sas_state::AcceptedProtocols;
35use tracing::{debug, error, trace};
36
37use super::{
38 CancelInfo, FlowId, IdentitiesBeingVerified, VerificationResult,
39 cache::RequestInfo,
40 event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent},
41 requests::RequestHandle,
42};
43use crate::{
44 Emoji,
45 identities::{DeviceData, UserIdentityData},
46 olm::StaticAccountData,
47 store::CryptoStoreError,
48 types::requests::{OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest},
49};
50
51#[derive(Clone, Debug)]
53pub struct Sas {
54 inner: SharedObservable<InnerSas>,
55 account: StaticAccountData,
56 identities_being_verified: IdentitiesBeingVerified,
57 flow_id: Arc<FlowId>,
58 we_started: bool,
59 request_handle: Option<RequestHandle>,
60}
61
62#[derive(Debug, Clone, Copy)]
63enum State {
64 Created,
65 Started,
66 Accepted,
67 WeAccepted,
68 KeyReceived,
69 KeySent,
70 KeysExchanged,
71 Confirmed,
72 MacReceived,
73 WaitingForDone,
74 Done,
75 Cancelled,
76}
77
78impl From<&InnerSas> for State {
79 fn from(value: &InnerSas) -> Self {
80 match value {
81 InnerSas::Created(_) => Self::Created,
82 InnerSas::Started(_) => Self::Started,
83 InnerSas::Accepted(_) => Self::Accepted,
84 InnerSas::WeAccepted(_) => Self::WeAccepted,
85 InnerSas::KeyReceived(_) => Self::KeyReceived,
86 InnerSas::KeySent(_) => Self::KeySent,
87 InnerSas::KeysExchanged(_) => Self::KeysExchanged,
88 InnerSas::Confirmed(_) => Self::Confirmed,
89 InnerSas::MacReceived(_) => Self::MacReceived,
90 InnerSas::WaitingForDone(_) => Self::WaitingForDone,
91 InnerSas::Done(_) => Self::Done,
92 InnerSas::Cancelled(_) => Self::Cancelled,
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
99pub struct EmojiShortAuthString {
100 pub indices: [u8; 7],
111
112 pub emojis: [Emoji; 7],
114}
115
116#[derive(Debug, Clone)]
118pub enum SasState {
119 Created {
122 protocols: SasV1Content,
125 },
126 Started {
129 protocols: SasV1Content,
132 },
133 Accepted {
136 accepted_protocols: AcceptedProtocols,
139 },
140 KeysExchanged {
143 emojis: Option<EmojiShortAuthString>,
146 decimals: (u16, u16, u16),
148 },
149 Confirmed,
152 Done {
154 verified_devices: Vec<DeviceData>,
156 verified_identities: Vec<UserIdentityData>,
158 },
159 Cancelled(CancelInfo),
161}
162
163impl PartialEq for SasState {
164 fn eq(&self, other: &Self) -> bool {
165 matches!(
166 (self, other),
167 (Self::Created { .. }, Self::Created { .. })
168 | (Self::Started { .. }, Self::Started { .. })
169 | (Self::Accepted { .. }, Self::Accepted { .. })
170 | (Self::KeysExchanged { .. }, Self::KeysExchanged { .. })
171 | (Self::Confirmed, Self::Confirmed)
172 | (Self::Done { .. }, Self::Done { .. })
173 | (Self::Cancelled(_), Self::Cancelled(_))
174 )
175 }
176}
177
178impl From<&InnerSas> for SasState {
179 fn from(value: &InnerSas) -> Self {
180 match value {
181 InnerSas::Created(s) => {
182 Self::Created { protocols: s.state.protocol_definitions.to_owned() }
183 }
184 InnerSas::Started(s) => {
185 Self::Started { protocols: s.state.protocol_definitions.to_owned() }
186 }
187 InnerSas::Accepted(s) => {
188 Self::Accepted { accepted_protocols: s.state.accepted_protocols.to_owned() }
189 }
190 InnerSas::WeAccepted(s) => {
191 Self::Accepted { accepted_protocols: s.state.accepted_protocols.to_owned() }
192 }
193 InnerSas::KeySent(s) => {
194 Self::Accepted { accepted_protocols: s.state.accepted_protocols.to_owned() }
195 }
196 InnerSas::KeyReceived(s) => {
197 Self::Accepted { accepted_protocols: s.state.accepted_protocols.to_owned() }
198 }
199 InnerSas::KeysExchanged(s) => {
200 let emojis = value.supports_emoji().then(|| {
201 let emojis = s.get_emoji();
202 let indices = s.get_emoji_index();
203
204 EmojiShortAuthString { emojis, indices }
205 });
206
207 let decimals = s.get_decimal();
208
209 Self::KeysExchanged { emojis, decimals }
210 }
211 InnerSas::MacReceived(s) => {
212 let emojis = value.supports_emoji().then(|| {
213 let emojis = s.get_emoji();
214 let indices = s.get_emoji_index();
215
216 EmojiShortAuthString { emojis, indices }
217 });
218
219 let decimals = s.get_decimal();
220
221 Self::KeysExchanged { emojis, decimals }
222 }
223 InnerSas::Confirmed(_) => Self::Confirmed,
224 InnerSas::WaitingForDone(_) => Self::Confirmed,
225 InnerSas::Done(s) => Self::Done {
226 verified_devices: s.verified_devices().to_vec(),
227 verified_identities: s.verified_identities().to_vec(),
228 },
229 InnerSas::Cancelled(c) => Self::Cancelled(c.state.as_ref().clone().into()),
230 }
231 }
232}
233
234impl Sas {
235 pub fn user_id(&self) -> &UserId {
237 &self.account.user_id
238 }
239
240 pub fn device_id(&self) -> &DeviceId {
242 &self.account.device_id
243 }
244
245 pub fn other_user_id(&self) -> &UserId {
247 self.identities_being_verified.other_user_id()
248 }
249
250 pub fn other_device_id(&self) -> &DeviceId {
252 self.identities_being_verified.other_device_id()
253 }
254
255 pub fn other_device(&self) -> &DeviceData {
257 self.identities_being_verified.other_device()
258 }
259
260 pub fn flow_id(&self) -> &FlowId {
262 &self.flow_id
263 }
264
265 pub fn room_id(&self) -> Option<&RoomId> {
267 as_variant!(self.flow_id(), FlowId::InRoom(r, _) => r)
268 }
269
270 pub fn supports_emoji(&self) -> bool {
273 self.inner.read().supports_emoji()
274 }
275
276 pub fn started_from_request(&self) -> bool {
278 self.inner.read().started_from_request()
279 }
280
281 pub fn is_self_verification(&self) -> bool {
283 self.identities_being_verified.is_self_verification()
284 }
285
286 pub fn have_we_confirmed(&self) -> bool {
288 self.inner.read().have_we_confirmed()
289 }
290
291 pub fn has_been_accepted(&self) -> bool {
293 self.inner.read().has_been_accepted()
294 }
295
296 pub fn cancel_info(&self) -> Option<CancelInfo> {
299 as_variant!(&*self.inner.read(), InnerSas::Cancelled(c) => {
300 c.state.as_ref().clone().into()
301 })
302 }
303
304 pub fn we_started(&self) -> bool {
306 self.we_started
307 }
308
309 #[cfg(test)]
310 #[allow(dead_code)]
311 pub(crate) fn set_creation_time(&self, time: ruma::time::Instant) {
312 self.inner.update(|inner| {
313 inner.set_creation_time(time);
314 });
315 }
316
317 fn start_helper(
318 flow_id: FlowId,
319 identities: IdentitiesBeingVerified,
320 we_started: bool,
321 request_handle: Option<RequestHandle>,
322 short_auth_strings: Option<Vec<ShortAuthenticationString>>,
323 ) -> (Sas, OutgoingContent) {
324 let (inner, content) = InnerSas::start(
325 identities.store.account.clone(),
326 identities.device_being_verified.clone(),
327 identities.own_identity.clone(),
328 identities.identity_being_verified.clone(),
329 flow_id.clone(),
330 request_handle.is_some(),
331 short_auth_strings,
332 );
333
334 let account = identities.store.account.clone();
335
336 (
337 Sas {
338 inner: SharedObservable::new(inner),
339 account,
340 identities_being_verified: identities,
341 flow_id: flow_id.into(),
342 we_started,
343 request_handle,
344 },
345 content,
346 )
347 }
348
349 pub(crate) fn start(
359 identities: IdentitiesBeingVerified,
360 transaction_id: OwnedTransactionId,
361 we_started: bool,
362 request_handle: Option<RequestHandle>,
363 short_auth_strings: Option<Vec<ShortAuthenticationString>>,
364 ) -> (Sas, OutgoingContent) {
365 let flow_id = FlowId::ToDevice(transaction_id);
366
367 Self::start_helper(flow_id, identities, we_started, request_handle, short_auth_strings)
368 }
369
370 #[allow(clippy::too_many_arguments)]
380 pub(crate) fn start_in_room(
381 flow_id: OwnedEventId,
382 room_id: OwnedRoomId,
383 identities: IdentitiesBeingVerified,
384 we_started: bool,
385 request_handle: RequestHandle,
386 ) -> (Sas, OutgoingContent) {
387 let flow_id = FlowId::InRoom(room_id, flow_id);
388 Self::start_helper(flow_id, identities, we_started, Some(request_handle), None)
389 }
390
391 pub(crate) fn from_start_event(
400 flow_id: FlowId,
401 content: &StartContent<'_>,
402 identities: IdentitiesBeingVerified,
403 request_handle: Option<RequestHandle>,
404 we_started: bool,
405 ) -> Result<Sas, OutgoingContent> {
406 let inner = InnerSas::from_start_event(
407 identities.store.account.clone(),
408 identities.device_being_verified.clone(),
409 flow_id.clone(),
410 content,
411 identities.own_identity.clone(),
412 identities.identity_being_verified.clone(),
413 request_handle.is_some(),
414 )?;
415
416 let account = identities.store.account.clone();
417
418 Ok(Sas {
419 inner: SharedObservable::new(inner),
420 account,
421 identities_being_verified: identities,
422 flow_id: flow_id.into(),
423 we_started,
424 request_handle,
425 })
426 }
427
428 pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
433 let protocols = as_variant!(self.state(), SasState::Started { protocols } => protocols)?;
434 let settings = AcceptSettings { allowed_methods: protocols.short_authentication_string };
435 self.accept_with_settings(settings)
436 }
437
438 pub fn accept_with_settings(
445 &self,
446 settings: AcceptSettings,
447 ) -> Option<OutgoingVerificationRequest> {
448 let old_state = self.state_debug();
449
450 let request = {
451 let mut guard = self.inner.write();
452 let sas: InnerSas = (*guard).clone();
453 let methods = settings.allowed_methods;
454
455 if let Some((sas, content)) = sas.accept(methods) {
456 ObservableWriteGuard::set(&mut guard, sas);
457
458 Some(match content {
459 OwnedAcceptContent::ToDevice(c) => {
460 let content = AnyToDeviceEventContent::KeyVerificationAccept(c);
461 self.content_to_request(&content).into()
462 }
463 OwnedAcceptContent::Room(room_id, content) => RoomMessageRequest {
464 room_id,
465 txn_id: TransactionId::new(),
466 content: Box::new(AnyMessageLikeEventContent::KeyVerificationAccept(
467 content,
468 )),
469 }
470 .into(),
471 })
472 } else {
473 None
474 }
475 };
476
477 let new_state = self.state_debug();
478
479 trace!(
480 flow_id = self.flow_id().as_str(),
481 ?old_state,
482 ?new_state,
483 "Accepted SAS verification"
484 );
485
486 request
487 }
488
489 pub async fn confirm(
497 &self,
498 ) -> Result<(Vec<OutgoingVerificationRequest>, Option<SignatureUploadRequest>), CryptoStoreError>
499 {
500 let (contents, done) = {
501 let mut guard = self.inner.write();
502
503 let sas: InnerSas = (*guard).clone();
504 let (sas, contents) = sas.confirm();
505
506 ObservableWriteGuard::set(&mut guard, sas);
507 (contents, guard.is_done())
508 };
509
510 let mac_requests = contents
511 .into_iter()
512 .map(|c| match c {
513 OutgoingContent::ToDevice(c) => self.content_to_request(&c).into(),
514 OutgoingContent::Room(r, c) => {
515 RoomMessageRequest { room_id: r, txn_id: TransactionId::new(), content: c }
516 .into()
517 }
518 })
519 .collect::<Vec<_>>();
520
521 if !mac_requests.is_empty() {
522 trace!(
523 user_id = ?self.other_user_id(),
524 device_id = ?self.other_device_id(),
525 "Confirming SAS verification"
526 )
527 }
528
529 if done {
530 match self.mark_as_done().await? {
531 VerificationResult::Cancel(c) => {
532 Ok((self.cancel_with_code(c).into_iter().collect(), None))
533 }
534 VerificationResult::Ok => Ok((mac_requests, None)),
535 VerificationResult::SignatureUpload(r) => Ok((mac_requests, Some(r))),
536 }
537 } else {
538 Ok((mac_requests, None))
539 }
540 }
541
542 pub(crate) async fn mark_as_done(&self) -> Result<VerificationResult, CryptoStoreError> {
543 self.identities_being_verified
544 .mark_as_done(self.verified_devices().as_deref(), self.verified_identities().as_deref())
545 .await
546 }
547
548 pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
555 self.cancel_with_code(CancelCode::User)
556 }
557
558 pub fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
572 let mut guard = self.inner.write();
573
574 if let Some(request) = &self.request_handle {
575 request.cancel_with_code(&code);
576 }
577
578 let sas: InnerSas = (*guard).clone();
579 let (sas, content) = sas.cancel(true, code);
580 ObservableWriteGuard::set(&mut guard, sas);
581
582 content.map(|c| match c {
583 OutgoingContent::Room(room_id, content) => {
584 RoomMessageRequest { room_id, txn_id: TransactionId::new(), content }.into()
585 }
586 OutgoingContent::ToDevice(c) => self.content_to_request(&c).into(),
587 })
588 }
589
590 pub(crate) fn cancel_if_timed_out(&self) -> Option<OutgoingVerificationRequest> {
591 if self.is_cancelled() || self.is_done() {
592 None
593 } else if self.timed_out() {
594 self.cancel_with_code(CancelCode::Timeout)
595 } else {
596 None
597 }
598 }
599
600 pub fn timed_out(&self) -> bool {
602 self.inner.read().timed_out()
603 }
604
605 pub fn can_be_presented(&self) -> bool {
607 self.inner.read().can_be_presented()
608 }
609
610 pub fn is_done(&self) -> bool {
612 self.inner.read().is_done()
613 }
614
615 pub fn is_cancelled(&self) -> bool {
617 self.inner.read().is_cancelled()
618 }
619
620 pub fn emoji(&self) -> Option<[Emoji; 7]> {
627 self.inner.read().emoji()
628 }
629
630 pub fn emoji_index(&self) -> Option<[u8; 7]> {
638 self.inner.read().emoji_index()
639 }
640
641 pub fn decimals(&self) -> Option<(u16, u16, u16)> {
647 self.inner.read().decimals()
648 }
649
650 pub fn changes(&self) -> impl Stream<Item = SasState> + use<> {
744 self.inner.subscribe().map(|s| (&s).into())
745 }
746
747 pub fn state(&self) -> SasState {
749 (&*self.inner.read()).into()
750 }
751
752 fn state_debug(&self) -> State {
753 (&*self.inner.read()).into()
754 }
755
756 pub(crate) fn receive_any_event(
757 &self,
758 sender: &UserId,
759 content: &AnyVerificationContent<'_>,
760 ) -> Option<(OutgoingContent, Option<RequestInfo>)> {
761 let old_state = self.state_debug();
762
763 let content = {
764 let mut guard = self.inner.write();
765 let sas: InnerSas = (*guard).clone();
766 let (sas, content) = sas.receive_any_event(sender, content);
767
768 ObservableWriteGuard::set(&mut guard, sas);
769
770 content
771 };
772
773 let new_state = self.state_debug();
774 trace!(
775 flow_id = self.flow_id().as_str(),
776 ?old_state,
777 ?new_state,
778 "SAS received an event and changed its state"
779 );
780
781 content
782 }
783
784 pub(crate) fn mark_request_as_sent(&self, request_id: &TransactionId) {
785 let old_state = self.state_debug();
786
787 {
788 let mut guard = self.inner.write();
789
790 let sas: InnerSas = (*guard).clone();
791
792 if let Some(sas) = sas.mark_request_as_sent(request_id) {
793 ObservableWriteGuard::set(&mut guard, sas);
794 } else {
795 error!(
796 flow_id = self.flow_id().as_str(),
797 ?request_id,
798 "Tried to mark a request as sent, but the request ID didn't match"
799 );
800 }
801 };
802
803 let new_state = self.state_debug();
804
805 debug!(
806 flow_id = self.flow_id().as_str(),
807 ?old_state,
808 ?new_state,
809 ?request_id,
810 "Marked a SAS verification HTTP request as sent"
811 );
812 }
813
814 pub(crate) fn verified_devices(&self) -> Option<Arc<[DeviceData]>> {
815 self.inner.read().verified_devices()
816 }
817
818 pub(crate) fn verified_identities(&self) -> Option<Arc<[UserIdentityData]>> {
819 self.inner.read().verified_identities()
820 }
821
822 pub(crate) fn content_to_request(&self, content: &AnyToDeviceEventContent) -> ToDeviceRequest {
823 ToDeviceRequest::with_id(
824 self.other_user_id(),
825 self.other_device_id().to_owned(),
826 content,
827 TransactionId::new(),
828 )
829 }
830}
831
832#[derive(Debug)]
834pub struct AcceptSettings {
835 allowed_methods: Vec<ShortAuthenticationString>,
836}
837
838impl Default for AcceptSettings {
839 fn default() -> Self {
841 Self {
842 allowed_methods: vec![
843 ShortAuthenticationString::Decimal,
844 ShortAuthenticationString::Emoji,
845 ],
846 }
847 }
848}
849
850impl AcceptSettings {
851 pub fn with_allowed_methods(methods: Vec<ShortAuthenticationString>) -> Self {
857 Self { allowed_methods: methods }
858 }
859}
860
861#[cfg(test)]
862mod tests {
863 use std::sync::Arc;
864
865 use assert_matches::assert_matches;
866 use matrix_sdk_test::async_test;
867 use ruma::{
868 DeviceId, TransactionId, UserId, device_id,
869 events::key::verification::{ShortAuthenticationString, accept::AcceptMethod},
870 user_id,
871 };
872 use strass::assert_let;
873 use tokio::sync::Mutex;
874
875 use super::Sas;
876 use crate::{
877 Account, DeviceData, SasState,
878 olm::PrivateCrossSigningIdentity,
879 store::{CryptoStoreWrapper, MemoryStore},
880 verification::{
881 VerificationStore,
882 event_enums::{AcceptContent, KeyContent, MacContent, OutgoingContent, StartContent},
883 },
884 };
885
886 fn alice_id() -> &'static UserId {
887 user_id!("@alice:example.org")
888 }
889
890 fn alice_device_id() -> &'static DeviceId {
891 device_id!("JLAFKJWSCS")
892 }
893
894 fn bob_id() -> &'static UserId {
895 user_id!("@bob:example.org")
896 }
897
898 fn bob_device_id() -> &'static DeviceId {
899 device_id!("BOBDEVICE")
900 }
901
902 fn machine_pair_test_helper() -> (VerificationStore, DeviceData, VerificationStore, DeviceData)
903 {
904 let alice = Account::with_device_id(alice_id(), alice_device_id());
905 let alice_device = DeviceData::from_account(&alice);
906
907 let bob = Account::with_device_id(bob_id(), bob_device_id());
908 let bob_device = DeviceData::from_account(&bob);
909
910 let alice_store = VerificationStore {
911 account: alice.static_data.clone(),
912 inner: Arc::new(CryptoStoreWrapper::new(
913 alice.user_id(),
914 alice_device_id(),
915 MemoryStore::new(),
916 )),
917 private_identity: Mutex::new(PrivateCrossSigningIdentity::empty(alice_id())).into(),
918 };
919
920 let bob_store = MemoryStore::new();
921 bob_store.save_devices(vec![alice_device.clone()]);
922
923 let bob_store = VerificationStore {
924 account: bob.static_data.clone(),
925 inner: Arc::new(CryptoStoreWrapper::new(bob.user_id(), bob_device_id(), bob_store)),
926 private_identity: Mutex::new(PrivateCrossSigningIdentity::empty(bob_id())).into(),
927 };
928
929 (alice_store, alice_device, bob_store, bob_device)
930 }
931
932 #[async_test]
933 async fn test_sas_wrapper_full() {
934 let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
935
936 let identities = alice_store.get_identities(bob_device).await.unwrap();
937
938 let (alice, content) = Sas::start(identities, TransactionId::new(), true, None, None);
939
940 assert_matches!(alice.state(), SasState::Created { .. });
941
942 let flow_id = alice.flow_id().to_owned();
943 let content = StartContent::try_from(&content).unwrap();
944
945 let identities = bob_store.get_identities(alice_device).await.unwrap();
946 let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
947
948 assert_matches!(bob.state(), SasState::Started { .. });
949
950 let request = bob.accept().unwrap();
951
952 let content = OutgoingContent::try_from(request).unwrap();
953 let content = AcceptContent::try_from(&content).unwrap();
954
955 let (content, request_info) =
956 alice.receive_any_event(bob.user_id(), &content.into()).unwrap();
957
958 assert_matches!(alice.state(), SasState::Accepted { .. });
959 assert_matches!(bob.state(), SasState::Accepted { .. });
960 assert!(!alice.can_be_presented());
961 assert!(!bob.can_be_presented());
962
963 alice.mark_request_as_sent(&request_info.unwrap().request_id);
964
965 let content = KeyContent::try_from(&content).unwrap();
966 let (content, request_info) =
967 bob.receive_any_event(alice.user_id(), &content.into()).unwrap();
968 assert!(!bob.can_be_presented());
969 assert_matches!(bob.state(), SasState::Accepted { .. });
970 bob.mark_request_as_sent(&request_info.unwrap().request_id);
971
972 assert!(bob.can_be_presented());
973 assert_matches!(bob.state(), SasState::KeysExchanged { .. });
974
975 let content = KeyContent::try_from(&content).unwrap();
976 alice.receive_any_event(bob.user_id(), &content.into());
977 assert_matches!(alice.state(), SasState::KeysExchanged { .. });
978 assert!(alice.can_be_presented());
979
980 assert_eq!(alice.emoji().unwrap(), bob.emoji().unwrap());
981 assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap());
982
983 let mut requests = alice.confirm().await.unwrap().0;
984 assert_matches!(alice.state(), SasState::Confirmed);
985 assert!(requests.len() == 1);
986 let request = requests.pop().unwrap();
987 let content = OutgoingContent::try_from(request).unwrap();
988 let content = MacContent::try_from(&content).unwrap();
989 bob.receive_any_event(alice.user_id(), &content.into());
990 assert_matches!(bob.state(), SasState::KeysExchanged { .. });
991
992 let mut requests = bob.confirm().await.unwrap().0;
993 assert_matches!(bob.state(), SasState::Done { .. });
994 assert!(requests.len() == 1);
995 let request = requests.pop().unwrap();
996 let content = OutgoingContent::try_from(request).unwrap();
997 let content = MacContent::try_from(&content).unwrap();
998 alice.receive_any_event(bob.user_id(), &content.into());
999
1000 assert!(alice.verified_devices().unwrap().contains(alice.other_device()));
1001 assert!(bob.verified_devices().unwrap().contains(bob.other_device()));
1002 assert_matches!(alice.state(), SasState::Done { .. });
1003 assert_matches!(bob.state(), SasState::Done { .. });
1004 }
1005
1006 #[async_test]
1007 async fn test_sas_with_restricted_methods() {
1008 let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
1009 let identities = alice_store.get_identities(bob_device).await.unwrap();
1010
1011 let short_auth_strings = vec![ShortAuthenticationString::Decimal];
1012 let (alice, content) =
1013 Sas::start(identities, TransactionId::new(), true, None, Some(short_auth_strings));
1014
1015 let flow_id = alice.flow_id().to_owned();
1016 let content = StartContent::try_from(&content).unwrap();
1017
1018 let identities = bob_store.get_identities(alice_device).await.unwrap();
1019 let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
1020
1021 let request = bob.accept().unwrap();
1022
1023 let content = OutgoingContent::try_from(request).unwrap();
1024 let content = AcceptContent::try_from(&content).unwrap();
1025 assert_let!(AcceptMethod::SasV1(content) = content.method());
1026
1027 assert!(content.short_authentication_string.contains(&ShortAuthenticationString::Decimal));
1028 assert!(!content.short_authentication_string.contains(&ShortAuthenticationString::Emoji));
1029 }
1030
1031 #[async_test]
1032 async fn test_sas_with_restricted_methods_does_not_present_emoji() {
1033 let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
1034 let identities = alice_store.get_identities(bob_device).await.unwrap();
1035
1036 let short_auth_strings = vec![ShortAuthenticationString::Decimal];
1037 let (alice, content) =
1038 Sas::start(identities, TransactionId::new(), true, None, Some(short_auth_strings));
1039
1040 let flow_id = alice.flow_id().to_owned();
1041 let content = StartContent::try_from(&content).unwrap();
1042
1043 let identities = bob_store.get_identities(alice_device).await.unwrap();
1044 let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
1045
1046 let request = bob.accept().unwrap();
1047
1048 let content = OutgoingContent::try_from(request).unwrap();
1049 let content = AcceptContent::try_from(&content).unwrap();
1050
1051 let (content, request_info) =
1052 alice.receive_any_event(bob.user_id(), &content.into()).unwrap();
1053 alice.mark_request_as_sent(&request_info.unwrap().request_id);
1054
1055 let content = KeyContent::try_from(&content).unwrap();
1056 let (content, request_info) =
1057 bob.receive_any_event(alice.user_id(), &content.into()).unwrap();
1058 bob.mark_request_as_sent(&request_info.unwrap().request_id);
1059
1060 let content = KeyContent::try_from(&content).unwrap();
1061 alice.receive_any_event(bob.user_id(), &content.into());
1062
1063 assert_matches!(alice.state(), SasState::KeysExchanged { .. });
1064 assert_matches!(bob.state(), SasState::KeysExchanged { .. });
1065 assert!(alice.can_be_presented());
1066 assert!(bob.can_be_presented());
1067
1068 assert!(!alice.supports_emoji());
1073 assert!(!bob.supports_emoji());
1074
1075 assert!(alice.emoji().is_none());
1076 assert!(bob.emoji().is_none());
1077 assert!(alice.emoji_index().is_none());
1078 assert!(bob.emoji_index().is_none());
1079
1080 assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap());
1082 }
1083}