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