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(
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 mut guard = self.inner.write();
577
578 if let Some(request) = &self.request_handle {
579 request.cancel_with_code(&code);
580 }
581
582 let sas: InnerSas = (*guard).clone();
583 let (sas, content) = sas.cancel(true, code);
584 ObservableWriteGuard::set(&mut guard, sas);
585
586 content.map(|c| match c {
587 OutgoingContent::Room(room_id, content) => {
588 RoomMessageRequest { room_id, txn_id: TransactionId::new(), content }.into()
589 }
590 OutgoingContent::ToDevice(c) => self.content_to_request(&c).into(),
591 })
592 }
593
594 pub(crate) fn cancel_if_timed_out(&self) -> Option<OutgoingVerificationRequest> {
595 if self.is_cancelled() || self.is_done() {
596 None
597 } else if self.timed_out() {
598 self.cancel_with_code(CancelCode::Timeout)
599 } else {
600 None
601 }
602 }
603
604 pub fn timed_out(&self) -> bool {
606 self.inner.read().timed_out()
607 }
608
609 pub fn can_be_presented(&self) -> bool {
611 self.inner.read().can_be_presented()
612 }
613
614 pub fn is_done(&self) -> bool {
616 self.inner.read().is_done()
617 }
618
619 pub fn is_cancelled(&self) -> bool {
621 self.inner.read().is_cancelled()
622 }
623
624 pub fn emoji(&self) -> Option<[Emoji; 7]> {
629 self.inner.read().emoji()
630 }
631
632 pub fn emoji_index(&self) -> Option<[u8; 7]> {
639 self.inner.read().emoji_index()
640 }
641
642 pub fn decimals(&self) -> Option<(u16, u16, u16)> {
648 self.inner.read().decimals()
649 }
650
651 pub fn changes(&self) -> impl Stream<Item = SasState> + use<> {
745 self.inner.subscribe().map(|s| (&s).into())
746 }
747
748 pub fn state(&self) -> SasState {
750 (&*self.inner.read()).into()
751 }
752
753 fn state_debug(&self) -> State {
754 (&*self.inner.read()).into()
755 }
756
757 pub(crate) fn receive_any_event(
758 &self,
759 sender: &UserId,
760 content: &AnyVerificationContent<'_>,
761 ) -> Option<(OutgoingContent, Option<RequestInfo>)> {
762 let old_state = self.state_debug();
763
764 let content = {
765 let mut guard = self.inner.write();
766 let sas: InnerSas = (*guard).clone();
767 let (sas, content) = sas.receive_any_event(sender, content);
768
769 ObservableWriteGuard::set(&mut guard, sas);
770
771 content
772 };
773
774 let new_state = self.state_debug();
775 trace!(
776 flow_id = self.flow_id().as_str(),
777 ?old_state,
778 ?new_state,
779 "SAS received an event and changed its state"
780 );
781
782 content
783 }
784
785 pub(crate) fn mark_request_as_sent(&self, request_id: &TransactionId) {
786 let old_state = self.state_debug();
787
788 {
789 let mut guard = self.inner.write();
790
791 let sas: InnerSas = (*guard).clone();
792
793 if let Some(sas) = sas.mark_request_as_sent(request_id) {
794 ObservableWriteGuard::set(&mut guard, sas);
795 } else {
796 error!(
797 flow_id = self.flow_id().as_str(),
798 ?request_id,
799 "Tried to mark a request as sent, but the request ID didn't match"
800 );
801 }
802 };
803
804 let new_state = self.state_debug();
805
806 debug!(
807 flow_id = self.flow_id().as_str(),
808 ?old_state,
809 ?new_state,
810 ?request_id,
811 "Marked a SAS verification HTTP request as sent"
812 );
813 }
814
815 pub(crate) fn verified_devices(&self) -> Option<Arc<[DeviceData]>> {
816 self.inner.read().verified_devices()
817 }
818
819 pub(crate) fn verified_identities(&self) -> Option<Arc<[UserIdentityData]>> {
820 self.inner.read().verified_identities()
821 }
822
823 pub(crate) fn content_to_request(&self, content: &AnyToDeviceEventContent) -> ToDeviceRequest {
824 ToDeviceRequest::with_id(
825 self.other_user_id(),
826 self.other_device_id().to_owned(),
827 content,
828 TransactionId::new(),
829 )
830 }
831}
832
833#[derive(Debug)]
835pub struct AcceptSettings {
836 allowed_methods: Vec<ShortAuthenticationString>,
837}
838
839impl Default for AcceptSettings {
840 fn default() -> Self {
842 Self {
843 allowed_methods: vec![
844 ShortAuthenticationString::Decimal,
845 ShortAuthenticationString::Emoji,
846 ],
847 }
848 }
849}
850
851impl AcceptSettings {
852 pub fn with_allowed_methods(methods: Vec<ShortAuthenticationString>) -> Self {
858 Self { allowed_methods: methods }
859 }
860}
861
862#[cfg(test)]
863mod tests {
864 use std::sync::Arc;
865
866 use assert_matches::assert_matches;
867 use assert_matches2::assert_let;
868 use matrix_sdk_test::async_test;
869 use ruma::{
870 DeviceId, TransactionId, UserId, device_id,
871 events::key::verification::{ShortAuthenticationString, accept::AcceptMethod},
872 user_id,
873 };
874 use tokio::sync::Mutex;
875
876 use super::Sas;
877 use crate::{
878 Account, DeviceData, SasState,
879 olm::PrivateCrossSigningIdentity,
880 store::{CryptoStoreWrapper, MemoryStore},
881 verification::{
882 VerificationStore,
883 event_enums::{AcceptContent, KeyContent, MacContent, OutgoingContent, StartContent},
884 },
885 };
886
887 fn alice_id() -> &'static UserId {
888 user_id!("@alice:example.org")
889 }
890
891 fn alice_device_id() -> &'static DeviceId {
892 device_id!("JLAFKJWSCS")
893 }
894
895 fn bob_id() -> &'static UserId {
896 user_id!("@bob:example.org")
897 }
898
899 fn bob_device_id() -> &'static DeviceId {
900 device_id!("BOBDEVICE")
901 }
902
903 fn machine_pair_test_helper() -> (VerificationStore, DeviceData, VerificationStore, DeviceData)
904 {
905 let alice = Account::with_device_id(alice_id(), alice_device_id());
906 let alice_device = DeviceData::from_account(&alice);
907
908 let bob = Account::with_device_id(bob_id(), bob_device_id());
909 let bob_device = DeviceData::from_account(&bob);
910
911 let alice_store = VerificationStore {
912 account: alice.static_data.clone(),
913 inner: Arc::new(CryptoStoreWrapper::new(
914 alice.user_id(),
915 alice_device_id(),
916 MemoryStore::new(),
917 )),
918 private_identity: Mutex::new(PrivateCrossSigningIdentity::empty(alice_id())).into(),
919 };
920
921 let bob_store = MemoryStore::new();
922 bob_store.save_devices(vec![alice_device.clone()]);
923
924 let bob_store = VerificationStore {
925 account: bob.static_data.clone(),
926 inner: Arc::new(CryptoStoreWrapper::new(bob.user_id(), bob_device_id(), bob_store)),
927 private_identity: Mutex::new(PrivateCrossSigningIdentity::empty(bob_id())).into(),
928 };
929
930 (alice_store, alice_device, bob_store, bob_device)
931 }
932
933 #[async_test]
934 async fn test_sas_wrapper_full() {
935 let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
936
937 let identities = alice_store.get_identities(bob_device).await.unwrap();
938
939 let (alice, content) = Sas::start(identities, TransactionId::new(), true, None, None);
940
941 assert_matches!(alice.state(), SasState::Created { .. });
942
943 let flow_id = alice.flow_id().to_owned();
944 let content = StartContent::try_from(&content).unwrap();
945
946 let identities = bob_store.get_identities(alice_device).await.unwrap();
947 let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
948
949 assert_matches!(bob.state(), SasState::Started { .. });
950
951 let request = bob.accept().unwrap();
952
953 let content = OutgoingContent::try_from(request).unwrap();
954 let content = AcceptContent::try_from(&content).unwrap();
955
956 let (content, request_info) =
957 alice.receive_any_event(bob.user_id(), &content.into()).unwrap();
958
959 assert_matches!(alice.state(), SasState::Accepted { .. });
960 assert_matches!(bob.state(), SasState::Accepted { .. });
961 assert!(!alice.can_be_presented());
962 assert!(!bob.can_be_presented());
963
964 alice.mark_request_as_sent(&request_info.unwrap().request_id);
965
966 let content = KeyContent::try_from(&content).unwrap();
967 let (content, request_info) =
968 bob.receive_any_event(alice.user_id(), &content.into()).unwrap();
969 assert!(!bob.can_be_presented());
970 assert_matches!(bob.state(), SasState::Accepted { .. });
971 bob.mark_request_as_sent(&request_info.unwrap().request_id);
972
973 assert!(bob.can_be_presented());
974 assert_matches!(bob.state(), SasState::KeysExchanged { .. });
975
976 let content = KeyContent::try_from(&content).unwrap();
977 alice.receive_any_event(bob.user_id(), &content.into());
978 assert_matches!(alice.state(), SasState::KeysExchanged { .. });
979 assert!(alice.can_be_presented());
980
981 assert_eq!(alice.emoji().unwrap(), bob.emoji().unwrap());
982 assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap());
983
984 let mut requests = alice.confirm().await.unwrap().0;
985 assert_matches!(alice.state(), SasState::Confirmed);
986 assert!(requests.len() == 1);
987 let request = requests.pop().unwrap();
988 let content = OutgoingContent::try_from(request).unwrap();
989 let content = MacContent::try_from(&content).unwrap();
990 bob.receive_any_event(alice.user_id(), &content.into());
991 assert_matches!(bob.state(), SasState::KeysExchanged { .. });
992
993 let mut requests = bob.confirm().await.unwrap().0;
994 assert_matches!(bob.state(), SasState::Done { .. });
995 assert!(requests.len() == 1);
996 let request = requests.pop().unwrap();
997 let content = OutgoingContent::try_from(request).unwrap();
998 let content = MacContent::try_from(&content).unwrap();
999 alice.receive_any_event(bob.user_id(), &content.into());
1000
1001 assert!(alice.verified_devices().unwrap().contains(alice.other_device()));
1002 assert!(bob.verified_devices().unwrap().contains(bob.other_device()));
1003 assert_matches!(alice.state(), SasState::Done { .. });
1004 assert_matches!(bob.state(), SasState::Done { .. });
1005 }
1006
1007 #[async_test]
1008 async fn test_sas_with_restricted_methods() {
1009 let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
1010 let identities = alice_store.get_identities(bob_device).await.unwrap();
1011
1012 let short_auth_strings = vec![ShortAuthenticationString::Decimal];
1013 let (alice, content) =
1014 Sas::start(identities, TransactionId::new(), true, None, Some(short_auth_strings));
1015
1016 let flow_id = alice.flow_id().to_owned();
1017 let content = StartContent::try_from(&content).unwrap();
1018
1019 let identities = bob_store.get_identities(alice_device).await.unwrap();
1020 let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
1021
1022 let request = bob.accept().unwrap();
1023
1024 let content = OutgoingContent::try_from(request).unwrap();
1025 let content = AcceptContent::try_from(&content).unwrap();
1026 assert_let!(AcceptMethod::SasV1(content) = content.method());
1027
1028 assert!(content.short_authentication_string.contains(&ShortAuthenticationString::Decimal));
1029 assert!(!content.short_authentication_string.contains(&ShortAuthenticationString::Emoji));
1030 }
1031}