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 api::client::keys::upload_signatures::v3::Request as SignatureUploadRequest,
28 events::{
29 key::verification::{cancel::CancelCode, start::SasV1Content, ShortAuthenticationString},
30 AnyMessageLikeEventContent, AnyToDeviceEventContent,
31 },
32 DeviceId, OwnedEventId, OwnedRoomId, OwnedTransactionId, RoomId, TransactionId, UserId,
33};
34pub use sas_state::AcceptedProtocols;
35use tracing::{debug, error, trace};
36
37use super::{
38 cache::RequestInfo,
39 event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent},
40 requests::RequestHandle,
41 CancelInfo, FlowId, IdentitiesBeingVerified, VerificationResult,
42};
43use crate::{
44 identities::{DeviceData, UserIdentityData},
45 olm::StaticAccountData,
46 store::CryptoStoreError,
47 types::requests::{OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest},
48 Emoji,
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(
360 identities: IdentitiesBeingVerified,
361 transaction_id: OwnedTransactionId,
362 we_started: bool,
363 request_handle: Option<RequestHandle>,
364 short_auth_strings: Option<Vec<ShortAuthenticationString>>,
365 ) -> (Sas, OutgoingContent) {
366 let flow_id = FlowId::ToDevice(transaction_id);
367
368 Self::start_helper(flow_id, identities, we_started, request_handle, short_auth_strings)
369 }
370
371 #[allow(clippy::too_many_arguments)]
382 pub(crate) fn start_in_room(
383 flow_id: OwnedEventId,
384 room_id: OwnedRoomId,
385 identities: IdentitiesBeingVerified,
386 we_started: bool,
387 request_handle: RequestHandle,
388 ) -> (Sas, OutgoingContent) {
389 let flow_id = FlowId::InRoom(room_id, flow_id);
390 Self::start_helper(flow_id, identities, we_started, Some(request_handle), None)
391 }
392
393 pub(crate) fn from_start_event(
404 flow_id: FlowId,
405 content: &StartContent<'_>,
406 identities: IdentitiesBeingVerified,
407 request_handle: Option<RequestHandle>,
408 we_started: bool,
409 ) -> Result<Sas, OutgoingContent> {
410 let inner = InnerSas::from_start_event(
411 identities.store.account.clone(),
412 identities.device_being_verified.clone(),
413 flow_id.clone(),
414 content,
415 identities.own_identity.clone(),
416 identities.identity_being_verified.clone(),
417 request_handle.is_some(),
418 )?;
419
420 let account = identities.store.account.clone();
421
422 Ok(Sas {
423 inner: SharedObservable::new(inner),
424 account,
425 identities_being_verified: identities,
426 flow_id: flow_id.into(),
427 we_started,
428 request_handle,
429 })
430 }
431
432 pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
437 let protocols = as_variant!(self.state(), SasState::Started { protocols } => protocols)?;
438 let settings = AcceptSettings { allowed_methods: protocols.short_authentication_string };
439 self.accept_with_settings(settings)
440 }
441
442 pub fn accept_with_settings(
449 &self,
450 settings: AcceptSettings,
451 ) -> Option<OutgoingVerificationRequest> {
452 let old_state = self.state_debug();
453
454 let request = {
455 let mut guard = self.inner.write();
456 let sas: InnerSas = (*guard).clone();
457 let methods = settings.allowed_methods;
458
459 if let Some((sas, content)) = sas.accept(methods) {
460 ObservableWriteGuard::set(&mut guard, sas);
461
462 Some(match content {
463 OwnedAcceptContent::ToDevice(c) => {
464 let content = AnyToDeviceEventContent::KeyVerificationAccept(c);
465 self.content_to_request(&content).into()
466 }
467 OwnedAcceptContent::Room(room_id, content) => RoomMessageRequest {
468 room_id,
469 txn_id: TransactionId::new(),
470 content: Box::new(AnyMessageLikeEventContent::KeyVerificationAccept(
471 content,
472 )),
473 }
474 .into(),
475 })
476 } else {
477 None
478 }
479 };
480
481 let new_state = self.state_debug();
482
483 trace!(
484 flow_id = self.flow_id().as_str(),
485 ?old_state,
486 ?new_state,
487 "Accepted SAS verification"
488 );
489
490 request
491 }
492
493 pub async fn confirm(
501 &self,
502 ) -> Result<(Vec<OutgoingVerificationRequest>, Option<SignatureUploadRequest>), CryptoStoreError>
503 {
504 let (contents, done) = {
505 let mut guard = self.inner.write();
506
507 let sas: InnerSas = (*guard).clone();
508 let (sas, contents) = sas.confirm();
509
510 ObservableWriteGuard::set(&mut guard, sas);
511 (contents, guard.is_done())
512 };
513
514 let mac_requests = contents
515 .into_iter()
516 .map(|c| match c {
517 OutgoingContent::ToDevice(c) => self.content_to_request(&c).into(),
518 OutgoingContent::Room(r, c) => {
519 RoomMessageRequest { room_id: r, txn_id: TransactionId::new(), content: c }
520 .into()
521 }
522 })
523 .collect::<Vec<_>>();
524
525 if !mac_requests.is_empty() {
526 trace!(
527 user_id = ?self.other_user_id(),
528 device_id = ?self.other_device_id(),
529 "Confirming SAS verification"
530 )
531 }
532
533 if done {
534 match self.mark_as_done().await? {
535 VerificationResult::Cancel(c) => {
536 Ok((self.cancel_with_code(c).into_iter().collect(), None))
537 }
538 VerificationResult::Ok => Ok((mac_requests, None)),
539 VerificationResult::SignatureUpload(r) => Ok((mac_requests, Some(r))),
540 }
541 } else {
542 Ok((mac_requests, None))
543 }
544 }
545
546 pub(crate) async fn mark_as_done(&self) -> Result<VerificationResult, CryptoStoreError> {
547 self.identities_being_verified
548 .mark_as_done(self.verified_devices().as_deref(), self.verified_identities().as_deref())
549 .await
550 }
551
552 pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
559 self.cancel_with_code(CancelCode::User)
560 }
561
562 pub fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
576 let content = {
577 let mut guard = self.inner.write();
578
579 if let Some(request) = &self.request_handle {
580 request.cancel_with_code(&code);
581 }
582
583 let sas: InnerSas = (*guard).clone();
584 let (sas, content) = sas.cancel(true, code);
585 ObservableWriteGuard::set(&mut guard, sas);
586
587 content.map(|c| match c {
588 OutgoingContent::Room(room_id, content) => {
589 RoomMessageRequest { room_id, txn_id: TransactionId::new(), content }.into()
590 }
591 OutgoingContent::ToDevice(c) => self.content_to_request(&c).into(),
592 })
593 };
594
595 content
596 }
597
598 pub(crate) fn cancel_if_timed_out(&self) -> Option<OutgoingVerificationRequest> {
599 if self.is_cancelled() || self.is_done() {
600 None
601 } else if self.timed_out() {
602 self.cancel_with_code(CancelCode::Timeout)
603 } else {
604 None
605 }
606 }
607
608 pub fn timed_out(&self) -> bool {
610 self.inner.read().timed_out()
611 }
612
613 pub fn can_be_presented(&self) -> bool {
615 self.inner.read().can_be_presented()
616 }
617
618 pub fn is_done(&self) -> bool {
620 self.inner.read().is_done()
621 }
622
623 pub fn is_cancelled(&self) -> bool {
625 self.inner.read().is_cancelled()
626 }
627
628 pub fn emoji(&self) -> Option<[Emoji; 7]> {
633 self.inner.read().emoji()
634 }
635
636 pub fn emoji_index(&self) -> Option<[u8; 7]> {
643 self.inner.read().emoji_index()
644 }
645
646 pub fn decimals(&self) -> Option<(u16, u16, u16)> {
652 self.inner.read().decimals()
653 }
654
655 pub fn changes(&self) -> impl Stream<Item = SasState> {
749 self.inner.subscribe().map(|s| (&s).into())
750 }
751
752 pub fn state(&self) -> SasState {
754 (&*self.inner.read()).into()
755 }
756
757 fn state_debug(&self) -> State {
758 (&*self.inner.read()).into()
759 }
760
761 pub(crate) fn receive_any_event(
762 &self,
763 sender: &UserId,
764 content: &AnyVerificationContent<'_>,
765 ) -> Option<(OutgoingContent, Option<RequestInfo>)> {
766 let old_state = self.state_debug();
767
768 let content = {
769 let mut guard = self.inner.write();
770 let sas: InnerSas = (*guard).clone();
771 let (sas, content) = sas.receive_any_event(sender, content);
772
773 ObservableWriteGuard::set(&mut guard, sas);
774
775 content
776 };
777
778 let new_state = self.state_debug();
779 trace!(
780 flow_id = self.flow_id().as_str(),
781 ?old_state,
782 ?new_state,
783 "SAS received an event and changed its state"
784 );
785
786 content
787 }
788
789 pub(crate) fn mark_request_as_sent(&self, request_id: &TransactionId) {
790 let old_state = self.state_debug();
791
792 {
793 let mut guard = self.inner.write();
794
795 let sas: InnerSas = (*guard).clone();
796
797 if let Some(sas) = sas.mark_request_as_sent(request_id) {
798 ObservableWriteGuard::set(&mut guard, sas);
799 } else {
800 error!(
801 flow_id = self.flow_id().as_str(),
802 ?request_id,
803 "Tried to mark a request as sent, but the request ID didn't match"
804 );
805 }
806 };
807
808 let new_state = self.state_debug();
809
810 debug!(
811 flow_id = self.flow_id().as_str(),
812 ?old_state,
813 ?new_state,
814 ?request_id,
815 "Marked a SAS verification HTTP request as sent"
816 );
817 }
818
819 pub(crate) fn verified_devices(&self) -> Option<Arc<[DeviceData]>> {
820 self.inner.read().verified_devices()
821 }
822
823 pub(crate) fn verified_identities(&self) -> Option<Arc<[UserIdentityData]>> {
824 self.inner.read().verified_identities()
825 }
826
827 pub(crate) fn content_to_request(&self, content: &AnyToDeviceEventContent) -> ToDeviceRequest {
828 ToDeviceRequest::with_id(
829 self.other_user_id(),
830 self.other_device_id().to_owned(),
831 content,
832 TransactionId::new(),
833 )
834 }
835}
836
837#[derive(Debug)]
839pub struct AcceptSettings {
840 allowed_methods: Vec<ShortAuthenticationString>,
841}
842
843impl Default for AcceptSettings {
844 fn default() -> Self {
846 Self {
847 allowed_methods: vec![
848 ShortAuthenticationString::Decimal,
849 ShortAuthenticationString::Emoji,
850 ],
851 }
852 }
853}
854
855impl AcceptSettings {
856 pub fn with_allowed_methods(methods: Vec<ShortAuthenticationString>) -> Self {
862 Self { allowed_methods: methods }
863 }
864}
865
866#[cfg(test)]
867mod tests {
868 use std::sync::Arc;
869
870 use assert_matches::assert_matches;
871 use assert_matches2::assert_let;
872 use matrix_sdk_test::async_test;
873 use ruma::{
874 device_id,
875 events::key::verification::{accept::AcceptMethod, ShortAuthenticationString},
876 user_id, DeviceId, TransactionId, UserId,
877 };
878 use tokio::sync::Mutex;
879
880 use super::Sas;
881 use crate::{
882 olm::PrivateCrossSigningIdentity,
883 store::{CryptoStoreWrapper, MemoryStore},
884 verification::{
885 event_enums::{AcceptContent, KeyContent, MacContent, OutgoingContent, StartContent},
886 VerificationStore,
887 },
888 Account, DeviceData, SasState,
889 };
890
891 fn alice_id() -> &'static UserId {
892 user_id!("@alice:example.org")
893 }
894
895 fn alice_device_id() -> &'static DeviceId {
896 device_id!("JLAFKJWSCS")
897 }
898
899 fn bob_id() -> &'static UserId {
900 user_id!("@bob:example.org")
901 }
902
903 fn bob_device_id() -> &'static DeviceId {
904 device_id!("BOBDEVICE")
905 }
906
907 fn machine_pair_test_helper() -> (VerificationStore, DeviceData, VerificationStore, DeviceData)
908 {
909 let alice = Account::with_device_id(alice_id(), alice_device_id());
910 let alice_device = DeviceData::from_account(&alice);
911
912 let bob = Account::with_device_id(bob_id(), bob_device_id());
913 let bob_device = DeviceData::from_account(&bob);
914
915 let alice_store = VerificationStore {
916 account: alice.static_data.clone(),
917 inner: Arc::new(CryptoStoreWrapper::new(
918 alice.user_id(),
919 alice_device_id(),
920 MemoryStore::new(),
921 )),
922 private_identity: Mutex::new(PrivateCrossSigningIdentity::empty(alice_id())).into(),
923 };
924
925 let bob_store = MemoryStore::new();
926 bob_store.save_devices(vec![alice_device.clone()]);
927
928 let bob_store = VerificationStore {
929 account: bob.static_data.clone(),
930 inner: Arc::new(CryptoStoreWrapper::new(bob.user_id(), bob_device_id(), bob_store)),
931 private_identity: Mutex::new(PrivateCrossSigningIdentity::empty(bob_id())).into(),
932 };
933
934 (alice_store, alice_device, bob_store, bob_device)
935 }
936
937 #[async_test]
938 async fn test_sas_wrapper_full() {
939 let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
940
941 let identities = alice_store.get_identities(bob_device).await.unwrap();
942
943 let (alice, content) = Sas::start(identities, TransactionId::new(), true, None, None);
944
945 assert_matches!(alice.state(), SasState::Created { .. });
946
947 let flow_id = alice.flow_id().to_owned();
948 let content = StartContent::try_from(&content).unwrap();
949
950 let identities = bob_store.get_identities(alice_device).await.unwrap();
951 let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
952
953 assert_matches!(bob.state(), SasState::Started { .. });
954
955 let request = bob.accept().unwrap();
956
957 let content = OutgoingContent::try_from(request).unwrap();
958 let content = AcceptContent::try_from(&content).unwrap();
959
960 let (content, request_info) =
961 alice.receive_any_event(bob.user_id(), &content.into()).unwrap();
962
963 assert_matches!(alice.state(), SasState::Accepted { .. });
964 assert_matches!(bob.state(), SasState::Accepted { .. });
965 assert!(!alice.can_be_presented());
966 assert!(!bob.can_be_presented());
967
968 alice.mark_request_as_sent(&request_info.unwrap().request_id);
969
970 let content = KeyContent::try_from(&content).unwrap();
971 let (content, request_info) =
972 bob.receive_any_event(alice.user_id(), &content.into()).unwrap();
973 assert!(!bob.can_be_presented());
974 assert_matches!(bob.state(), SasState::Accepted { .. });
975 bob.mark_request_as_sent(&request_info.unwrap().request_id);
976
977 assert!(bob.can_be_presented());
978 assert_matches!(bob.state(), SasState::KeysExchanged { .. });
979
980 let content = KeyContent::try_from(&content).unwrap();
981 alice.receive_any_event(bob.user_id(), &content.into());
982 assert_matches!(alice.state(), SasState::KeysExchanged { .. });
983 assert!(alice.can_be_presented());
984
985 assert_eq!(alice.emoji().unwrap(), bob.emoji().unwrap());
986 assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap());
987
988 let mut requests = alice.confirm().await.unwrap().0;
989 assert_matches!(alice.state(), SasState::Confirmed);
990 assert!(requests.len() == 1);
991 let request = requests.pop().unwrap();
992 let content = OutgoingContent::try_from(request).unwrap();
993 let content = MacContent::try_from(&content).unwrap();
994 bob.receive_any_event(alice.user_id(), &content.into());
995 assert_matches!(bob.state(), SasState::KeysExchanged { .. });
996
997 let mut requests = bob.confirm().await.unwrap().0;
998 assert_matches!(bob.state(), SasState::Done { .. });
999 assert!(requests.len() == 1);
1000 let request = requests.pop().unwrap();
1001 let content = OutgoingContent::try_from(request).unwrap();
1002 let content = MacContent::try_from(&content).unwrap();
1003 alice.receive_any_event(bob.user_id(), &content.into());
1004
1005 assert!(alice.verified_devices().unwrap().contains(alice.other_device()));
1006 assert!(bob.verified_devices().unwrap().contains(bob.other_device()));
1007 assert_matches!(alice.state(), SasState::Done { .. });
1008 assert_matches!(bob.state(), SasState::Done { .. });
1009 }
1010
1011 #[async_test]
1012 async fn test_sas_with_restricted_methods() {
1013 let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
1014 let identities = alice_store.get_identities(bob_device).await.unwrap();
1015
1016 let short_auth_strings = vec![ShortAuthenticationString::Decimal];
1017 let (alice, content) =
1018 Sas::start(identities, TransactionId::new(), true, None, Some(short_auth_strings));
1019
1020 let flow_id = alice.flow_id().to_owned();
1021 let content = StartContent::try_from(&content).unwrap();
1022
1023 let identities = bob_store.get_identities(alice_device).await.unwrap();
1024 let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
1025
1026 let request = bob.accept().unwrap();
1027
1028 let content = OutgoingContent::try_from(request).unwrap();
1029 let content = AcceptContent::try_from(&content).unwrap();
1030 assert_let!(AcceptMethod::SasV1(content) = content.method());
1031
1032 assert!(content.short_authentication_string.contains(&ShortAuthenticationString::Decimal));
1033 assert!(!content.short_authentication_string.contains(&ShortAuthenticationString::Emoji));
1034 }
1035}