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