Skip to main content

matrix_sdk_crypto_ffi/
verification.rs

1use std::sync::Arc;
2
3use futures_util::{Stream, StreamExt};
4use matrix_sdk_common::executor::Handle;
5use matrix_sdk_crypto::{
6    CancelInfo as RustCancelInfo, QrVerification as InnerQr, QrVerificationState, Sas as InnerSas,
7    SasState as RustSasState, Verification as InnerVerification,
8    VerificationRequest as InnerVerificationRequest,
9    VerificationRequestState as RustVerificationRequestState,
10    matrix_sdk_qrcode::QrVerificationData,
11};
12use ruma::events::key::verification::VerificationMethod;
13use vodozemac::{base64_decode, base64_encode};
14
15use crate::{CryptoStoreError, OutgoingVerificationRequest, SignatureUploadRequest};
16
17/// Listener that will be passed over the FFI to report changes to a SAS
18/// verification.
19#[matrix_sdk_ffi_macros::export(callback_interface)]
20pub trait SasListener: Send {
21    /// The callback that should be called on the Rust side
22    ///
23    /// # Arguments
24    ///
25    /// * `state` - The current state of the SAS verification.
26    fn on_change(&self, state: SasState);
27}
28
29/// An Enum describing the state the SAS verification is in.
30#[derive(uniffi::Enum)]
31pub enum SasState {
32    /// The verification has been created, the protocols that should be used
33    /// have been proposed to the other party.
34    Created,
35    /// The verification has been started, the other party proposed the
36    /// protocols that should be used and that can be accepted.
37    Started,
38    /// The verification has been accepted and both sides agreed to a set of
39    /// protocols that will be used for the verification process.
40    Accepted,
41    /// The public keys have been exchanged and the short auth string can be
42    /// presented to the user.
43    KeysExchanged {
44        /// The emojis that represent the short auth string, will be `None` if
45        /// the emoji SAS method wasn't one of accepted protocols.
46        emojis: Option<Vec<i32>>,
47        /// The list of decimals that represent the short auth string.
48        decimals: Vec<i32>,
49    },
50    /// The verification process has been confirmed from our side, we're waiting
51    /// for the other side to confirm as well.
52    Confirmed,
53    /// The verification process has been successfully concluded.
54    Done,
55    /// The verification process has been cancelled.
56    Cancelled {
57        /// Information about the reason of the cancellation.
58        cancel_info: CancelInfo,
59    },
60}
61
62impl From<RustSasState> for SasState {
63    fn from(s: RustSasState) -> Self {
64        match s {
65            RustSasState::Created { .. } => Self::Created,
66            RustSasState::Started { .. } => Self::Started,
67            RustSasState::Accepted { .. } => Self::Accepted,
68            RustSasState::KeysExchanged { emojis, decimals } => Self::KeysExchanged {
69                emojis: emojis.map(|e| e.indices.map(|i| i as i32).to_vec()),
70                decimals: [decimals.0.into(), decimals.1.into(), decimals.2.into()].to_vec(),
71            },
72            RustSasState::Confirmed => Self::Confirmed,
73            RustSasState::Done { .. } => Self::Done,
74            RustSasState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
75        }
76    }
77}
78
79/// Enum representing the different verification flows we support.
80#[derive(uniffi::Object)]
81pub struct Verification {
82    pub(crate) inner: InnerVerification,
83    pub(crate) runtime: Handle,
84}
85
86#[matrix_sdk_ffi_macros::export]
87impl Verification {
88    /// Try to represent the `Verification` as an `Sas` verification object,
89    /// returns `None` if the verification is not a `Sas` verification.
90    pub fn as_sas(&self) -> Option<Arc<Sas>> {
91        if let InnerVerification::SasV1(sas) = &self.inner {
92            Some(Sas { inner: sas.clone(), runtime: self.runtime.to_owned() }.into())
93        } else {
94            None
95        }
96    }
97
98    /// Try to represent the `Verification` as an `QrCode` verification object,
99    /// returns `None` if the verification is not a `QrCode` verification.
100    pub fn as_qr(&self) -> Option<Arc<QrCode>> {
101        if let InnerVerification::QrV1(qr) = &self.inner {
102            Some(QrCode { inner: qr.clone(), runtime: self.runtime.to_owned() }.into())
103        } else {
104            None
105        }
106    }
107}
108
109/// The `m.sas.v1` verification flow.
110#[derive(uniffi::Object)]
111pub struct Sas {
112    pub(crate) inner: Box<InnerSas>,
113    pub(crate) runtime: Handle,
114}
115
116#[matrix_sdk_ffi_macros::export]
117impl Sas {
118    /// Get the user id of the other side.
119    pub fn other_user_id(&self) -> String {
120        self.inner.other_user_id().to_string()
121    }
122
123    /// Get the device ID of the other side.
124    pub fn other_device_id(&self) -> String {
125        self.inner.other_device_id().to_string()
126    }
127
128    /// Get the unique ID that identifies this SAS verification flow.
129    pub fn flow_id(&self) -> String {
130        self.inner.flow_id().as_str().to_owned()
131    }
132
133    /// Get the room id if the verification is happening inside a room.
134    pub fn room_id(&self) -> Option<String> {
135        self.inner.room_id().map(|r| r.to_string())
136    }
137
138    /// Is the SAS flow done.
139    pub fn is_done(&self) -> bool {
140        self.inner.is_done()
141    }
142
143    /// Did we initiate the verification flow.
144    pub fn we_started(&self) -> bool {
145        self.inner.we_started()
146    }
147
148    /// Accept that we're going forward with the short auth string verification.
149    pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
150        self.inner.accept().map(|r| r.into())
151    }
152
153    /// Confirm a verification was successful.
154    ///
155    /// This method should be called if a short auth string should be confirmed
156    /// as matching.
157    pub fn confirm(&self) -> Result<Option<ConfirmVerificationResult>, CryptoStoreError> {
158        let (requests, signature_request) = self.runtime.block_on(self.inner.confirm())?;
159
160        let requests = requests.into_iter().map(|r| r.into()).collect();
161
162        Ok(Some(ConfirmVerificationResult {
163            requests,
164            signature_request: signature_request.map(|s| s.into()),
165        }))
166    }
167
168    /// Cancel the SAS verification using the given cancel code.
169    ///
170    /// # Arguments
171    ///
172    /// * `cancel_code` - The error code for why the verification was cancelled,
173    ///   manual cancellatio usually happens with `m.user` cancel code. The full
174    ///   list of cancel codes can be found in the [spec]
175    ///
176    /// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
177    pub fn cancel(&self, cancel_code: String) -> Option<OutgoingVerificationRequest> {
178        self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
179    }
180
181    /// Get a list of emoji indices of the emoji representation of the short
182    /// auth string.
183    ///
184    /// *Note*: A SAS verification needs to be started and in the presentable
185    /// state for this to return the list of emoji indices, otherwise returns
186    /// `None`.
187    pub fn get_emoji_indices(&self) -> Option<Vec<i32>> {
188        self.inner.emoji_index().map(|v| v.iter().map(|i| (*i).into()).collect())
189    }
190
191    /// Get the decimal representation of the short auth string.
192    ///
193    /// *Note*: A SAS verification needs to be started and in the presentable
194    /// state for this to return the list of decimals, otherwise returns
195    /// `None`.
196    pub fn get_decimals(&self) -> Option<Vec<i32>> {
197        self.inner.decimals().map(|v| [v.0.into(), v.1.into(), v.2.into()].to_vec())
198    }
199
200    /// Set a listener for changes in the SAS verification process.
201    ///
202    /// The given callback will be called whenever the state changes.
203    ///
204    /// This method can be used to react to changes in the state of the
205    /// verification process, or rather the method can be used to handle
206    /// each step of the verification process.
207    ///
208    /// This method will spawn a tokio task on the Rust side, once we reach the
209    /// Done or Cancelled state, the task will stop listening for changes.
210    ///
211    /// # Flowchart
212    ///
213    /// The flow of the verification process is pictured below. Please note
214    /// that the process can be cancelled at each step of the process.
215    /// Either side can cancel the process.
216    ///
217    /// ```text
218    ///                ┌───────┐
219    ///                │Started│
220    ///                └───┬───┘
221    ///                    │
222    ///               ┌────⌄───┐
223    ///               │Accepted│
224    ///               └────┬───┘
225    ///                    │
226    ///            ┌───────⌄──────┐
227    ///            │Keys Exchanged│
228    ///            └───────┬──────┘
229    ///                    │
230    ///            ________⌄________
231    ///           ╱                 ╲       ┌─────────┐
232    ///          ╱   Does the short  ╲______│Cancelled│
233    ///          ╲ auth string match ╱ no   └─────────┘
234    ///           ╲_________________╱
235    ///                    │yes
236    ///                    │
237    ///               ┌────⌄────┐
238    ///               │Confirmed│
239    ///               └────┬────┘
240    ///                    │
241    ///                ┌───⌄───┐
242    ///                │  Done │
243    ///                └───────┘
244    /// ```
245    pub fn set_changes_listener(&self, listener: Box<dyn SasListener>) {
246        let stream = self.inner.changes();
247
248        self.runtime.spawn(Self::changes_listener(stream, listener));
249    }
250
251    /// Get the current state of the SAS verification process.
252    pub fn state(&self) -> SasState {
253        self.inner.state().into()
254    }
255}
256
257impl Sas {
258    async fn changes_listener(
259        mut stream: impl Stream<Item = RustSasState> + std::marker::Unpin,
260        listener: Box<dyn SasListener>,
261    ) {
262        while let Some(state) = stream.next().await {
263            // If we receive a done or a cancelled state we're at the end of our road, we
264            // break out of the loop to deallocate the stream and finish the
265            // task.
266            let should_break =
267                matches!(state, RustSasState::Done { .. } | RustSasState::Cancelled { .. });
268
269            listener.on_change(state.into());
270
271            if should_break {
272                break;
273            }
274        }
275    }
276}
277
278/// Listener that will be passed over the FFI to report changes to a QrCode
279/// verification.
280#[matrix_sdk_ffi_macros::export(callback_interface)]
281pub trait QrCodeListener: Send {
282    /// The callback that should be called on the Rust side
283    ///
284    /// # Arguments
285    ///
286    /// * `state` - The current state of the QrCode verification.
287    fn on_change(&self, state: QrCodeState);
288}
289
290/// An Enum describing the state the QrCode verification is in.
291#[derive(uniffi::Enum)]
292pub enum QrCodeState {
293    /// The QR verification has been started.
294    Started,
295    /// The QR verification has been scanned by the other side.
296    Scanned,
297    /// The scanning of the QR code has been confirmed by us.
298    Confirmed,
299    /// We have successfully scanned the QR code and are able to send a
300    /// reciprocation event.
301    Reciprocated,
302    /// The verification process has been successfully concluded.
303    Done,
304    /// The verification process has been cancelled.
305    Cancelled {
306        /// Information about the reason of the cancellation.
307        cancel_info: CancelInfo,
308    },
309}
310
311impl From<QrVerificationState> for QrCodeState {
312    fn from(value: QrVerificationState) -> Self {
313        match value {
314            QrVerificationState::Started => Self::Started,
315            QrVerificationState::Scanned => Self::Scanned,
316            QrVerificationState::Confirmed => Self::Confirmed,
317            QrVerificationState::Reciprocated => Self::Reciprocated,
318            QrVerificationState::Done { .. } => Self::Done,
319            QrVerificationState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
320        }
321    }
322}
323
324/// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1`
325/// verification flow.
326#[derive(uniffi::Object)]
327pub struct QrCode {
328    pub(crate) inner: Box<InnerQr>,
329    pub(crate) runtime: Handle,
330}
331
332#[matrix_sdk_ffi_macros::export]
333impl QrCode {
334    /// Get the user id of the other side.
335    pub fn other_user_id(&self) -> String {
336        self.inner.other_user_id().to_string()
337    }
338
339    /// Get the device ID of the other side.
340    pub fn other_device_id(&self) -> String {
341        self.inner.other_device_id().to_string()
342    }
343
344    /// Get the unique ID that identifies this QR code verification flow.
345    pub fn flow_id(&self) -> String {
346        self.inner.flow_id().as_str().to_owned()
347    }
348
349    /// Get the room id if the verification is happening inside a room.
350    pub fn room_id(&self) -> Option<String> {
351        self.inner.room_id().map(|r| r.to_string())
352    }
353
354    /// Is the QR code verification done.
355    pub fn is_done(&self) -> bool {
356        self.inner.is_done()
357    }
358
359    /// Has the verification flow been cancelled.
360    pub fn is_cancelled(&self) -> bool {
361        self.inner.is_cancelled()
362    }
363
364    /// Did we initiate the verification flow.
365    pub fn we_started(&self) -> bool {
366        self.inner.we_started()
367    }
368
369    /// Get the CancelInfo of this QR code verification object.
370    ///
371    /// Will be `None` if the flow has not been cancelled.
372    pub fn cancel_info(&self) -> Option<CancelInfo> {
373        self.inner.cancel_info().map(|c| c.into())
374    }
375
376    /// Has the QR verification been scanned by the other side.
377    ///
378    /// When the verification object is in this state it's required that the
379    /// user confirms that the other side has scanned the QR code.
380    pub fn has_been_scanned(&self) -> bool {
381        self.inner.has_been_scanned()
382    }
383
384    /// Have we successfully scanned the QR code and are able to send a
385    /// reciprocation event.
386    pub fn reciprocated(&self) -> bool {
387        self.inner.reciprocated()
388    }
389
390    /// Cancel the QR code verification using the given cancel code.
391    ///
392    /// # Arguments
393    ///
394    /// * `cancel_code` - The error code for why the verification was cancelled,
395    ///   manual cancellatio usually happens with `m.user` cancel code. The full
396    ///   list of cancel codes can be found in the [spec]
397    ///
398    /// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
399    pub fn cancel(&self, cancel_code: String) -> Option<OutgoingVerificationRequest> {
400        self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
401    }
402
403    /// Confirm a verification was successful.
404    ///
405    /// This method should be called if we want to confirm that the other side
406    /// has scanned our QR code.
407    pub fn confirm(&self) -> Option<ConfirmVerificationResult> {
408        self.inner.confirm_scanning().map(|r| ConfirmVerificationResult {
409            requests: vec![r.into()],
410            signature_request: None,
411        })
412    }
413
414    /// Generate data that should be encoded as a QR code.
415    ///
416    /// This method should be called right before a QR code should be displayed,
417    /// the returned data is base64 encoded (without padding) and needs to be
418    /// decoded on the other side before it can be put through a QR code
419    /// generator.
420    pub fn generate_qr_code(&self) -> Option<String> {
421        self.inner.to_bytes().map(base64_encode).ok()
422    }
423
424    /// Set a listener for changes in the QrCode verification process.
425    ///
426    /// The given callback will be called whenever the state changes.
427    pub fn set_changes_listener(&self, listener: Box<dyn QrCodeListener>) {
428        let stream = self.inner.changes();
429
430        self.runtime.spawn(Self::changes_listener(stream, listener));
431    }
432
433    /// Get the current state of the QrCode verification process.
434    pub fn state(&self) -> QrCodeState {
435        self.inner.state().into()
436    }
437}
438
439impl QrCode {
440    async fn changes_listener(
441        mut stream: impl Stream<Item = QrVerificationState> + std::marker::Unpin,
442        listener: Box<dyn QrCodeListener>,
443    ) {
444        while let Some(state) = stream.next().await {
445            // If we receive a done or a cancelled state we're at the end of our road, we
446            // break out of the loop to deallocate the stream and finish the
447            // task.
448            let should_break = matches!(
449                state,
450                QrVerificationState::Done { .. } | QrVerificationState::Cancelled { .. }
451            );
452
453            listener.on_change(state.into());
454
455            if should_break {
456                break;
457            }
458        }
459    }
460}
461
462/// Information on why a verification flow has been cancelled and by whom.
463#[derive(uniffi::Record)]
464pub struct CancelInfo {
465    /// The textual representation of the cancel reason
466    pub reason: String,
467    /// The code describing the cancel reason
468    pub cancel_code: String,
469    /// Was the verification flow cancelled by us
470    pub cancelled_by_us: bool,
471}
472
473impl From<RustCancelInfo> for CancelInfo {
474    fn from(c: RustCancelInfo) -> Self {
475        Self {
476            reason: c.reason().to_owned(),
477            cancel_code: c.cancel_code().to_string(),
478            cancelled_by_us: c.cancelled_by_us(),
479        }
480    }
481}
482
483/// A result type for starting SAS verifications.
484#[derive(uniffi::Record)]
485pub struct StartSasResult {
486    /// The SAS verification object that got created.
487    pub sas: Arc<Sas>,
488    /// The request that needs to be sent out to notify the other side that a
489    /// SAS verification should start.
490    pub request: OutgoingVerificationRequest,
491}
492
493/// A result type for scanning QR codes.
494#[derive(uniffi::Record)]
495pub struct ScanResult {
496    /// The QR code verification object that got created.
497    pub qr: Arc<QrCode>,
498    /// The request that needs to be sent out to notify the other side that a
499    /// QR code verification should start.
500    pub request: OutgoingVerificationRequest,
501}
502
503/// A result type for requesting verifications.
504#[derive(uniffi::Record)]
505pub struct RequestVerificationResult {
506    /// The verification request object that got created.
507    pub verification: Arc<VerificationRequest>,
508    /// The request that needs to be sent out to notify the other side that
509    /// we're requesting verification to begin.
510    pub request: OutgoingVerificationRequest,
511}
512
513/// A result type for confirming verifications.
514#[derive(uniffi::Record)]
515pub struct ConfirmVerificationResult {
516    /// The requests that needs to be sent out to notify the other side that we
517    /// confirmed the verification.
518    pub requests: Vec<OutgoingVerificationRequest>,
519    /// A request that will upload signatures of the verified device or user, if
520    /// the verification is completed and we're able to sign devices or users
521    pub signature_request: Option<SignatureUploadRequest>,
522}
523
524/// Listener that will be passed over the FFI to report changes to a
525/// verification request.
526#[matrix_sdk_ffi_macros::export(callback_interface)]
527pub trait VerificationRequestListener: Send {
528    /// The callback that should be called on the Rust side
529    ///
530    /// # Arguments
531    ///
532    /// * `state` - The current state of the verification request.
533    fn on_change(&self, state: VerificationRequestState);
534}
535
536/// An Enum describing the state the QrCode verification is in.
537#[derive(uniffi::Enum)]
538pub enum VerificationRequestState {
539    /// The verification request was sent
540    Requested,
541    /// The verification request is ready to start a verification flow.
542    Ready {
543        /// The verification methods supported by the other side.
544        their_methods: Vec<String>,
545
546        /// The verification methods supported by the us.
547        our_methods: Vec<String>,
548    },
549    /// The verification flow that was started with this request has finished.
550    Done,
551    /// The verification process has been cancelled.
552    Cancelled {
553        /// Information about the reason of the cancellation.
554        cancel_info: CancelInfo,
555    },
556}
557
558/// The verificatoin request object which then can transition into some concrete
559/// verification method
560#[derive(uniffi::Object)]
561pub struct VerificationRequest {
562    pub(crate) inner: InnerVerificationRequest,
563    pub(crate) runtime: Handle,
564}
565
566#[matrix_sdk_ffi_macros::export]
567impl VerificationRequest {
568    /// The id of the other user that is participating in this verification
569    /// request.
570    pub fn other_user_id(&self) -> String {
571        self.inner.other_user().to_string()
572    }
573
574    /// The id of the other device that is participating in this verification.
575    pub fn other_device_id(&self) -> Option<String> {
576        self.inner.other_device_id().map(|d| d.to_string())
577    }
578
579    /// Get the unique ID of this verification request
580    pub fn flow_id(&self) -> String {
581        self.inner.flow_id().as_str().to_owned()
582    }
583
584    /// Get the room id if the verification is happening inside a room.
585    pub fn room_id(&self) -> Option<String> {
586        self.inner.room_id().map(|r| r.to_string())
587    }
588
589    /// Has the verification flow that was started with this request finished.
590    pub fn is_done(&self) -> bool {
591        self.inner.is_done()
592    }
593
594    /// Is the verification request ready to start a verification flow.
595    pub fn is_ready(&self) -> bool {
596        self.inner.is_ready()
597    }
598
599    /// Did we initiate the verification request
600    pub fn we_started(&self) -> bool {
601        self.inner.we_started()
602    }
603
604    /// Has the verification request been answered by another device.
605    pub fn is_passive(&self) -> bool {
606        self.inner.is_passive()
607    }
608
609    /// Has the verification flow that been cancelled.
610    pub fn is_cancelled(&self) -> bool {
611        self.inner.is_cancelled()
612    }
613
614    /// Get info about the cancellation if the verification request has been
615    /// cancelled.
616    pub fn cancel_info(&self) -> Option<CancelInfo> {
617        self.inner.cancel_info().map(|v| v.into())
618    }
619
620    /// Get the supported verification methods of the other side.
621    ///
622    /// Will be present only if the other side requested the verification or if
623    /// we're in the ready state.
624    pub fn their_supported_methods(&self) -> Option<Vec<String>> {
625        self.inner.their_supported_methods().map(|m| m.iter().map(|m| m.to_string()).collect())
626    }
627
628    /// Get our own supported verification methods that we advertised.
629    ///
630    /// Will be present only we requested the verification or if we're in the
631    /// ready state.
632    pub fn our_supported_methods(&self) -> Option<Vec<String>> {
633        self.inner.our_supported_methods().map(|m| m.iter().map(|m| m.to_string()).collect())
634    }
635
636    /// Accept a verification requests that we share with the given user with
637    /// the given flow id.
638    ///
639    /// This will move the verification request into the ready state.
640    ///
641    /// # Arguments
642    ///
643    /// * `user_id` - The ID of the user for which we would like to accept the
644    ///   verification requests.
645    ///
646    /// * `flow_id` - The ID that uniquely identifies the verification flow.
647    ///
648    /// * `methods` - A list of verification methods that we want to advertise
649    ///   as supported.
650    pub fn accept(&self, methods: Vec<String>) -> Option<OutgoingVerificationRequest> {
651        let methods = methods.into_iter().map(VerificationMethod::from).collect();
652        self.inner.accept_with_methods(methods).map(|r| r.into())
653    }
654
655    /// Cancel a verification for the given user with the given flow id using
656    /// the given cancel code.
657    pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
658        self.inner.cancel().map(|r| r.into())
659    }
660
661    /// Transition from a verification request into short auth string based
662    /// verification.
663    ///
664    /// # Arguments
665    ///
666    /// * `user_id` - The ID of the user for which we would like to start the
667    ///   SAS verification.
668    ///
669    /// * `flow_id` - The ID of the verification request that initiated the
670    ///   verification flow.
671    pub fn start_sas_verification(&self) -> Result<Option<StartSasResult>, CryptoStoreError> {
672        Ok(self.runtime.block_on(self.inner.start_sas())?.map(|(sas, r)| StartSasResult {
673            sas: Arc::new(Sas { inner: Box::new(sas), runtime: self.runtime.clone() }),
674            request: r.into(),
675        }))
676    }
677
678    /// Transition from a verification request into QR code verification.
679    ///
680    /// This method should be called when one wants to display a QR code so the
681    /// other side can scan it and move the QR code verification forward.
682    ///
683    /// # Arguments
684    ///
685    /// * `user_id` - The ID of the user for which we would like to start the QR
686    ///   code verification.
687    ///
688    /// * `flow_id` - The ID of the verification request that initiated the
689    ///   verification flow.
690    pub fn start_qr_verification(&self) -> Result<Option<Arc<QrCode>>, CryptoStoreError> {
691        Ok(self
692            .runtime
693            .block_on(self.inner.generate_qr_code())?
694            .map(|qr| QrCode { inner: Box::new(qr), runtime: self.runtime.clone() }.into()))
695    }
696
697    /// Pass data from a scanned QR code to an active verification request and
698    /// transition into QR code verification.
699    ///
700    /// This requires an active `VerificationRequest` to succeed, returns `None`
701    /// if no `VerificationRequest` is found or if the QR code data is invalid.
702    ///
703    /// # Arguments
704    ///
705    /// * `user_id` - The ID of the user for which we would like to start the QR
706    ///   code verification.
707    ///
708    /// * `flow_id` - The ID of the verification request that initiated the
709    ///   verification flow.
710    ///
711    /// * `data` - The data that was extracted from the scanned QR code as an
712    ///   base64 encoded string, without padding.
713    pub fn scan_qr_code(&self, data: String) -> Option<ScanResult> {
714        let data = base64_decode(data).ok()?;
715        let data = QrVerificationData::from_bytes(data).ok()?;
716
717        if let Some(qr) = self.runtime.block_on(self.inner.scan_qr_code(data)).ok()? {
718            let request = qr.reciprocate()?;
719
720            Some(ScanResult {
721                qr: QrCode { inner: Box::new(qr), runtime: self.runtime.clone() }.into(),
722                request: request.into(),
723            })
724        } else {
725            None
726        }
727    }
728
729    /// Set a listener for changes in the verification request
730    ///
731    /// The given callback will be called whenever the state changes.
732    pub fn set_changes_listener(&self, listener: Box<dyn VerificationRequestListener>) {
733        let stream = self.inner.changes();
734
735        self.runtime.spawn(Self::changes_listener(self.inner.to_owned(), stream, listener));
736    }
737
738    /// Get the current state of the verification request.
739    pub fn state(&self) -> VerificationRequestState {
740        Self::convert_verification_request(&self.inner, self.inner.state())
741    }
742}
743
744impl VerificationRequest {
745    fn convert_verification_request(
746        request: &InnerVerificationRequest,
747        value: RustVerificationRequestState,
748    ) -> VerificationRequestState {
749        match value {
750            // The clients do not need to distinguish `Created` and `Requested` state
751            RustVerificationRequestState::Created { .. } => VerificationRequestState::Requested,
752            RustVerificationRequestState::Requested { .. } => VerificationRequestState::Requested,
753            RustVerificationRequestState::Ready {
754                their_methods,
755                our_methods,
756                other_device_data: _,
757            } => VerificationRequestState::Ready {
758                their_methods: their_methods.iter().map(|m| m.to_string()).collect(),
759                our_methods: our_methods.iter().map(|m| m.to_string()).collect(),
760            },
761            RustVerificationRequestState::Done => VerificationRequestState::Done,
762            RustVerificationRequestState::Transitioned { .. } => {
763                let their_methods = request
764                    .their_supported_methods()
765                    .expect("The transitioned state should know the other side's methods")
766                    .into_iter()
767                    .map(|m| m.to_string())
768                    .collect();
769                let our_methods = request
770                    .our_supported_methods()
771                    .expect("The transitioned state should know our own supported methods")
772                    .iter()
773                    .map(|m| m.to_string())
774                    .collect();
775                VerificationRequestState::Ready { their_methods, our_methods }
776            }
777
778            RustVerificationRequestState::Cancelled(c) => {
779                VerificationRequestState::Cancelled { cancel_info: c.into() }
780            }
781        }
782    }
783
784    async fn changes_listener(
785        request: InnerVerificationRequest,
786        mut stream: impl Stream<Item = RustVerificationRequestState> + std::marker::Unpin,
787        listener: Box<dyn VerificationRequestListener>,
788    ) {
789        while let Some(state) = stream.next().await {
790            // If we receive a done or a cancelled state we're at the end of our road, we
791            // break out of the loop to deallocate the stream and finish the
792            // task.
793            let should_break = matches!(
794                state,
795                RustVerificationRequestState::Done | RustVerificationRequestState::Cancelled { .. }
796            );
797
798            let state = Self::convert_verification_request(&request, state);
799
800            listener.on_change(state);
801
802            if should_break {
803                break;
804            }
805        }
806    }
807}