matrix_sdk_crypto_ffi/
verification.rs

1use std::sync::Arc;
2
3use futures_util::{Stream, StreamExt};
4use matrix_sdk_crypto::{
5    matrix_sdk_qrcode::QrVerificationData, CancelInfo as RustCancelInfo, QrVerification as InnerQr,
6    QrVerificationState, Sas as InnerSas, SasState as RustSasState,
7    Verification as InnerVerification, VerificationRequest as InnerVerificationRequest,
8    VerificationRequestState as RustVerificationRequestState,
9};
10use ruma::events::key::verification::VerificationMethod;
11use tokio::runtime::Handle;
12use vodozemac::{base64_decode, base64_encode};
13
14use crate::{CryptoStoreError, OutgoingVerificationRequest, SignatureUploadRequest};
15
16/// Listener that will be passed over the FFI to report changes to a SAS
17/// verification.
18#[matrix_sdk_ffi_macros::export(callback_interface)]
19pub trait SasListener: Send {
20    /// The callback that should be called on the Rust side
21    ///
22    /// # Arguments
23    ///
24    /// * `state` - The current state of the SAS verification.
25    fn on_change(&self, state: SasState);
26}
27
28/// An Enum describing the state the SAS verification is in.
29#[derive(uniffi::Enum)]
30pub enum SasState {
31    /// The verification has been created, the protocols that should be used
32    /// have been proposed to the other party.
33    Created,
34    /// The verification has been started, the other party proposed the
35    /// protocols that should be used and that can be accepted.
36    Started,
37    /// The verification has been accepted and both sides agreed to a set of
38    /// protocols that will be used for the verification process.
39    Accepted,
40    /// The public keys have been exchanged and the short auth string can be
41    /// presented to the user.
42    KeysExchanged {
43        /// The emojis that represent the short auth string, will be `None` if
44        /// the emoji SAS method wasn't one of accepted protocols.
45        emojis: Option<Vec<i32>>,
46        /// The list of decimals that represent the short auth string.
47        decimals: Vec<i32>,
48    },
49    /// The verification process has been confirmed from our side, we're waiting
50    /// for the other side to confirm as well.
51    Confirmed,
52    /// The verification process has been successfully concluded.
53    Done,
54    /// The verification process has been cancelled.
55    Cancelled {
56        /// Information about the reason of the cancellation.
57        cancel_info: CancelInfo,
58    },
59}
60
61impl From<RustSasState> for SasState {
62    fn from(s: RustSasState) -> Self {
63        match s {
64            RustSasState::Created { .. } => Self::Created,
65            RustSasState::Started { .. } => Self::Started,
66            RustSasState::Accepted { .. } => Self::Accepted,
67            RustSasState::KeysExchanged { emojis, decimals } => Self::KeysExchanged {
68                emojis: emojis.map(|e| e.indices.map(|i| i as i32).to_vec()),
69                decimals: [decimals.0.into(), decimals.1.into(), decimals.2.into()].to_vec(),
70            },
71            RustSasState::Confirmed => Self::Confirmed,
72            RustSasState::Done { .. } => Self::Done,
73            RustSasState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
74        }
75    }
76}
77
78/// Enum representing the different verification flows we support.
79#[derive(uniffi::Object)]
80pub struct Verification {
81    pub(crate) inner: InnerVerification,
82    pub(crate) runtime: Handle,
83}
84
85#[matrix_sdk_ffi_macros::export]
86impl Verification {
87    /// Try to represent the `Verification` as an `Sas` verification object,
88    /// returns `None` if the verification is not a `Sas` verification.
89    pub fn as_sas(&self) -> Option<Arc<Sas>> {
90        if let InnerVerification::SasV1(sas) = &self.inner {
91            Some(Sas { inner: sas.to_owned(), runtime: self.runtime.to_owned() }.into())
92        } else {
93            None
94        }
95    }
96
97    /// Try to represent the `Verification` as an `QrCode` verification object,
98    /// returns `None` if the verification is not a `QrCode` verification.
99    pub fn as_qr(&self) -> Option<Arc<QrCode>> {
100        if let InnerVerification::QrV1(qr) = &self.inner {
101            Some(QrCode { inner: qr.to_owned(), runtime: self.runtime.to_owned() }.into())
102        } else {
103            None
104        }
105    }
106}
107
108/// The `m.sas.v1` verification flow.
109#[derive(uniffi::Object)]
110pub struct Sas {
111    pub(crate) inner: InnerSas,
112    pub(crate) runtime: Handle,
113}
114
115#[matrix_sdk_ffi_macros::export]
116impl Sas {
117    /// Get the user id of the other side.
118    pub fn other_user_id(&self) -> String {
119        self.inner.other_user_id().to_string()
120    }
121
122    /// Get the device ID of the other side.
123    pub fn other_device_id(&self) -> String {
124        self.inner.other_device_id().to_string()
125    }
126
127    /// Get the unique ID that identifies this SAS verification flow.
128    pub fn flow_id(&self) -> String {
129        self.inner.flow_id().as_str().to_owned()
130    }
131
132    /// Get the room id if the verification is happening inside a room.
133    pub fn room_id(&self) -> Option<String> {
134        self.inner.room_id().map(|r| r.to_string())
135    }
136
137    /// Is the SAS flow done.
138    pub fn is_done(&self) -> bool {
139        self.inner.is_done()
140    }
141
142    /// Did we initiate the verification flow.
143    pub fn we_started(&self) -> bool {
144        self.inner.we_started()
145    }
146
147    /// Accept that we're going forward with the short auth string verification.
148    pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
149        self.inner.accept().map(|r| r.into())
150    }
151
152    /// Confirm a verification was successful.
153    ///
154    /// This method should be called if a short auth string should be confirmed
155    /// as matching.
156    pub fn confirm(&self) -> Result<Option<ConfirmVerificationResult>, CryptoStoreError> {
157        let (requests, signature_request) = self.runtime.block_on(self.inner.confirm())?;
158
159        let requests = requests.into_iter().map(|r| r.into()).collect();
160
161        Ok(Some(ConfirmVerificationResult {
162            requests,
163            signature_request: signature_request.map(|s| s.into()),
164        }))
165    }
166
167    /// Cancel the SAS verification using the given cancel code.
168    ///
169    /// # Arguments
170    ///
171    /// * `cancel_code` - The error code for why the verification was cancelled,
172    ///   manual cancellatio usually happens with `m.user` cancel code. The full
173    ///   list of cancel codes can be found in the [spec]
174    ///
175    /// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
176    pub fn cancel(&self, cancel_code: String) -> Option<OutgoingVerificationRequest> {
177        self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
178    }
179
180    /// Get a list of emoji indices of the emoji representation of the short
181    /// auth string.
182    ///
183    /// *Note*: A SAS verification needs to be started and in the presentable
184    /// state for this to return the list of emoji indices, otherwise returns
185    /// `None`.
186    pub fn get_emoji_indices(&self) -> Option<Vec<i32>> {
187        self.inner.emoji_index().map(|v| v.iter().map(|i| (*i).into()).collect())
188    }
189
190    /// Get the decimal representation of the short auth string.
191    ///
192    /// *Note*: A SAS verification needs to be started and in the presentable
193    /// state for this to return the list of decimals, otherwise returns
194    /// `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
205    /// each 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 bellow. Please note
213    /// that the process can be cancelled at each step of the process.
214    /// Either side 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 road, we
263            // break out of the loop to deallocate the stream and finish the
264            // 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: 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 road, we
445            // break out of the loop to deallocate the stream and finish the
446            // 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
498    /// QR 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    ///
647    /// * `methods` - A list of verification methods that we want to advertise
648    ///   as supported.
649    pub fn accept(&self, methods: Vec<String>) -> Option<OutgoingVerificationRequest> {
650        let methods = methods.into_iter().map(VerificationMethod::from).collect();
651        self.inner.accept_with_methods(methods).map(|r| r.into())
652    }
653
654    /// Cancel a verification for the given user with the given flow id using
655    /// the given cancel code.
656    pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
657        self.inner.cancel().map(|r| r.into())
658    }
659
660    /// Transition from a verification request into short auth string based
661    /// verification.
662    ///
663    /// # Arguments
664    ///
665    /// * `user_id` - The ID of the user for which we would like to start the
666    ///   SAS verification.
667    ///
668    /// * `flow_id` - The ID of the verification request that initiated the
669    ///   verification flow.
670    pub fn start_sas_verification(&self) -> Result<Option<StartSasResult>, CryptoStoreError> {
671        Ok(self.runtime.block_on(self.inner.start_sas())?.map(|(sas, r)| StartSasResult {
672            sas: Arc::new(Sas { inner: sas, runtime: self.runtime.clone() }),
673            request: r.into(),
674        }))
675    }
676
677    /// Transition from a verification request into QR code verification.
678    ///
679    /// This method should be called when one wants to display a QR code so the
680    /// other side can scan it and move the QR code verification forward.
681    ///
682    /// # Arguments
683    ///
684    /// * `user_id` - The ID of the user for which we would like to start the QR
685    ///   code verification.
686    ///
687    /// * `flow_id` - The ID of the verification request that initiated the
688    ///   verification flow.
689    pub fn start_qr_verification(&self) -> Result<Option<Arc<QrCode>>, CryptoStoreError> {
690        Ok(self
691            .runtime
692            .block_on(self.inner.generate_qr_code())?
693            .map(|qr| QrCode { inner: qr, runtime: self.runtime.clone() }.into()))
694    }
695
696    /// Pass data from a scanned QR code to an active verification request and
697    /// transition into QR code verification.
698    ///
699    /// This requires an active `VerificationRequest` to succeed, returns `None`
700    /// if no `VerificationRequest` is found or if the QR code data is invalid.
701    ///
702    /// # Arguments
703    ///
704    /// * `user_id` - The ID of the user for which we would like to start the QR
705    ///   code verification.
706    ///
707    /// * `flow_id` - The ID of the verification request that initiated the
708    ///   verification flow.
709    ///
710    /// * `data` - The data that was extracted from the scanned QR code as an
711    ///   base64 encoded string, without padding.
712    pub fn scan_qr_code(&self, data: String) -> Option<ScanResult> {
713        let data = base64_decode(data).ok()?;
714        let data = QrVerificationData::from_bytes(data).ok()?;
715
716        if let Some(qr) = self.runtime.block_on(self.inner.scan_qr_code(data)).ok()? {
717            let request = qr.reciprocate()?;
718
719            Some(ScanResult {
720                qr: QrCode { inner: qr, runtime: self.runtime.clone() }.into(),
721                request: request.into(),
722            })
723        } else {
724            None
725        }
726    }
727
728    /// Set a listener for changes in the verification request
729    ///
730    /// The given callback will be called whenever the state changes.
731    pub fn set_changes_listener(&self, listener: Box<dyn VerificationRequestListener>) {
732        let stream = self.inner.changes();
733
734        self.runtime.spawn(Self::changes_listener(self.inner.to_owned(), stream, listener));
735    }
736
737    /// Get the current state of the verification request.
738    pub fn state(&self) -> VerificationRequestState {
739        Self::convert_verification_request(&self.inner, self.inner.state())
740    }
741}
742
743impl VerificationRequest {
744    fn convert_verification_request(
745        request: &InnerVerificationRequest,
746        value: RustVerificationRequestState,
747    ) -> VerificationRequestState {
748        match value {
749            // The clients do not need to distinguish `Created` and `Requested` state
750            RustVerificationRequestState::Created { .. } => VerificationRequestState::Requested,
751            RustVerificationRequestState::Requested { .. } => VerificationRequestState::Requested,
752            RustVerificationRequestState::Ready {
753                their_methods,
754                our_methods,
755                other_device_data: _,
756            } => VerificationRequestState::Ready {
757                their_methods: their_methods.iter().map(|m| m.to_string()).collect(),
758                our_methods: our_methods.iter().map(|m| m.to_string()).collect(),
759            },
760            RustVerificationRequestState::Done => VerificationRequestState::Done,
761            RustVerificationRequestState::Transitioned { .. } => {
762                let their_methods = request
763                    .their_supported_methods()
764                    .expect("The transitioned state should know the other side's methods")
765                    .into_iter()
766                    .map(|m| m.to_string())
767                    .collect();
768                let our_methods = request
769                    .our_supported_methods()
770                    .expect("The transitioned state should know our own supported methods")
771                    .iter()
772                    .map(|m| m.to_string())
773                    .collect();
774                VerificationRequestState::Ready { their_methods, our_methods }
775            }
776
777            RustVerificationRequestState::Cancelled(c) => {
778                VerificationRequestState::Cancelled { cancel_info: c.into() }
779            }
780        }
781    }
782
783    async fn changes_listener(
784        request: InnerVerificationRequest,
785        mut stream: impl Stream<Item = RustVerificationRequestState> + std::marker::Unpin,
786        listener: Box<dyn VerificationRequestListener>,
787    ) {
788        while let Some(state) = stream.next().await {
789            // If we receive a done or a cancelled state we're at the end of our road, we
790            // break out of the loop to deallocate the stream and finish the
791            // task.
792            let should_break = matches!(
793                state,
794                RustVerificationRequestState::Done | RustVerificationRequestState::Cancelled { .. }
795            );
796
797            let state = Self::convert_verification_request(&request, state);
798
799            listener.on_change(state);
800
801            if should_break {
802                break;
803            }
804        }
805    }
806}