1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::{
    os::raw::{c_uint, c_void},
    pin::Pin,
    sync::{Arc, Mutex},
    time::Duration,
};

use ffi_sdk::BoxedLoginProvider;

use crate::{auth::authenticator::DittoAuthenticator, utils::prelude::*};

/// Implement this trait for a type in order to construct an
/// [`OnlineWithAuthentication`](crate::prelude::OnlineWithAuthentication) identity.
// You can find an example in tests::common::mod
pub trait DittoAuthenticationEventHandler: Send + Sync {
    /// This will be called when you need to authenticate.
    /// Usually it will involve a call to `auth.login_with_token`
    fn authentication_required(&self, auth: DittoAuthenticator);

    /// Allows for custom behavior hooks when authentication is expiring
    fn authentication_expiring_soon(&self, auth: DittoAuthenticator, seconds_remaining: Duration);
}

pub(crate) struct LoginProvider {
    pub(crate) _provider: BoxedLoginProvider,
    pub(crate) ctx: Arc<Mutex<LoginProviderCtx>>,
}

// this inner type allows us to get a reference/ptr before the outer type is
// constructed
pub(crate) struct LoginProviderCtx {
    auth_event_handler: Pin<Box<dyn DittoAuthenticationEventHandler + 'static>>,
    authenticator: Option<DittoAuthenticator>,
    #[allow(dead_code)] // called across the FFI
    cached_expiry_time: Option<u32>,
}

impl LoginProvider {
    pub fn new(handler: Box<dyn DittoAuthenticationEventHandler + 'static>) -> Self {
        let ctx = LoginProviderCtx {
            auth_event_handler: handler.into(),
            authenticator: None,
            cached_expiry_time: None,
        };

        let arc_ctx = Arc::new(Mutex::new(ctx));
        // let raw_context = Arc::into_raw(arc_ctx.clone()) as *mut c_void; // this
        // calls mem::forget internally Across the FFI, this ptr will be
        // immediately `retained` establishing shared ownership of
        // the LoginProviderCtx across the FFI
        let raw_context = Arc::as_ptr(&arc_ctx) as *mut c_void;

        let c_provider = unsafe {
            ffi_sdk::ditto_auth_client_make_login_provider(
                raw_context,
                Some(LoginProviderCtx::retain),
                Some(LoginProviderCtx::release),
                LoginProviderCtx::authentication_expiring,
            )
        };

        LoginProvider {
            _provider: c_provider,
            ctx: arc_ctx,
        }
    }
}

impl LoginProviderCtx {
    pub(crate) extern "C" fn retain(ctx: *mut c_void) {
        // the ctx* ptr here will (and must) have a layout determined by Arc::into_raw
        // The cast should target the inner type of the Arc, which is in this case a
        // Mutex<LoginProviderCtx> so the Arc::increment_strong_count hits the correct
        // offset
        // If the type of LoginProvider.ctx changes in the future, this must be updated
        let ptr = ctx.cast::<Mutex<LoginProviderCtx>>();
        unsafe { Arc::increment_strong_count(ptr) }; // internally calls
                                                     // Arc::from_raw
    }

    pub(crate) extern "C" fn release(ctx: *mut c_void) {
        let ptr = ctx.cast::<Mutex<LoginProviderCtx>>();
        unsafe {
            Arc::decrement_strong_count(ptr);
        } // internally calls Arc::from_raw
    }

    pub(crate) extern "C" fn authentication_expiring(ctx: *mut c_void, seconds_remaining: c_uint) {
        let ctx_ptr: *const Mutex<LoginProviderCtx> = ctx.cast();
        let arc_ctx: &Mutex<LoginProviderCtx> = unsafe { &*ctx_ptr }; // Copies the ptr to the inner type on the heap, then borrow
        let mut ctx_ref = arc_ctx.lock().expect("LoginProvider Mutex is poisoned"); // typically caused by panic while holding this lock
        match &ctx_ref.authenticator {
            Some(authn) => {
                if seconds_remaining == 0 {
                    ctx_ref
                        .auth_event_handler
                        .authentication_required(authn.retain());
                } else {
                    let duration = Duration::from_secs(seconds_remaining.into());
                    ctx_ref
                        .auth_event_handler
                        .authentication_expiring_soon(authn.retain(), duration);
                }
            }
            None => ctx_ref.cached_expiry_time = Some(seconds_remaining),
        }
    }
    pub(crate) fn set_authenticator(&mut self, authenticator: DittoAuthenticator) {
        self.authenticator = Some(authenticator);
        if let Some(authn) = &self.authenticator {
            if let Some(time) = self.cached_expiry_time {
                // easier than trying to call across the FFI
                if time == 0 {
                    self.auth_event_handler
                        .authentication_required(authn.retain());
                } else {
                    let duration = Duration::from_secs(time.into());
                    self.auth_event_handler
                        .authentication_expiring_soon(authn.retain(), duration);
                }
                self.cached_expiry_time = None; // set to None after calling
                                                // either method
            }
        }
    }
}