use std::pin::Pin; use chrono::{DateTime, Utc}; use futures::{Future, future::FusedFuture}; #[cfg(target_os = "android")] use mullvad_types::account::PlayPurchasePaymentToken; use mullvad_types::{account::VoucherSubmission, device::Device, wireguard::WireguardData}; use super::{Error, PrivateAccountAndDevice, ResponseTx}; pub(crate) struct CurrentApiCall { current_call: Option, } impl CurrentApiCall { pub fn new() -> Self { Self { current_call: None } } pub fn clear(&mut self) { self.current_call = None; } pub fn set_login(&mut self, login: ApiCall, tx: ResponseTx<()>) { self.current_call = Some(Call::Login(login, Some(tx))); } pub fn set_oneshot_rotation(&mut self, rotation: ApiCall) { self.current_call = Some(Call::OneshotKeyRotation(rotation)); } pub fn set_timed_rotation(&mut self, rotation: ApiCall) { self.current_call = Some(Call::TimerKeyRotation(rotation)); } pub fn set_validation(&mut self, validation: ApiCall) { self.current_call = Some(Call::Validation(validation)); } pub fn set_expiry_check(&mut self, expiry_call: ApiCall>) { self.current_call = Some(Call::ExpiryCheck(expiry_call)); } pub fn set_voucher_submission( &mut self, voucher_call: ApiCall, tx: ResponseTx, ) { self.current_call = Some(Call::VoucherSubmission(voucher_call, Some(tx))); } #[cfg(target_os = "android")] pub fn set_init_play_purchase( &mut self, init_play_purchase_call: ApiCall, tx: ResponseTx, ) { self.current_call = Some(Call::InitPlayPurchase(init_play_purchase_call, Some(tx))); } #[cfg(target_os = "android")] pub fn set_verify_play_purchase( &mut self, verify_play_purchase_call: ApiCall<()>, tx: ResponseTx<()>, ) { self.current_call = Some(Call::VerifyPlayPurchase( verify_play_purchase_call, Some(tx), )); } pub fn is_validating(&self) -> bool { matches!( &self.current_call, Some(Call::Validation(_)) | Some(Call::OneshotKeyRotation(_)) ) } pub fn is_checking_expiry(&self) -> bool { matches!(&self.current_call, Some(Call::ExpiryCheck(_))) } pub fn is_running_timed_totation(&self) -> bool { matches!(&self.current_call, Some(Call::TimerKeyRotation(_))) } pub fn is_idle(&self) -> bool { self.current_call.is_none() } pub fn is_logging_in(&self) -> bool { use Call::*; matches!(&self.current_call, Some(Login(..))) } } impl Future for CurrentApiCall { type Output = ApiResult; fn poll( mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll { match self.current_call.as_mut() { Some(call) => { let result = Pin::new(call).poll(cx); if result.is_ready() { self.current_call = None; } result } None => panic!("Polled an unfinished future"), } } } impl FusedFuture for CurrentApiCall { fn is_terminated(&self) -> bool { self.current_call.is_none() } } type ApiCall = Pin> + Send>>; enum Call { Login(ApiCall, Option>), TimerKeyRotation(ApiCall), OneshotKeyRotation(ApiCall), Validation(ApiCall), VoucherSubmission( ApiCall, Option>, ), #[cfg(target_os = "android")] InitPlayPurchase( ApiCall, Option>, ), #[cfg(target_os = "android")] VerifyPlayPurchase(ApiCall<()>, Option>), ExpiryCheck(ApiCall>), } impl futures::Future for Call { type Output = ApiResult; fn poll( mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll { use Call::*; match &mut *self { Login(call, tx) => match Pin::new(call).poll(cx) { std::task::Poll::Ready(response) => { std::task::Poll::Ready(ApiResult::Login(response, tx.take().unwrap())) } _ => std::task::Poll::Pending, }, TimerKeyRotation(call) | OneshotKeyRotation(call) => { Pin::new(call).poll(cx).map(ApiResult::Rotation) } Validation(call) => Pin::new(call).poll(cx).map(ApiResult::Validation), VoucherSubmission(call, tx) => match Pin::new(call).poll(cx) { std::task::Poll::Ready(response) => std::task::Poll::Ready( ApiResult::VoucherSubmission(response, tx.take().unwrap()), ), _ => std::task::Poll::Pending, }, #[cfg(target_os = "android")] InitPlayPurchase(call, tx) => { if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) { std::task::Poll::Ready(ApiResult::InitPlayPurchase( response, tx.take().unwrap(), )) } else { std::task::Poll::Pending } } #[cfg(target_os = "android")] VerifyPlayPurchase(call, tx) => { if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) { std::task::Poll::Ready(ApiResult::VerifyPlayPurchase( response, tx.take().unwrap(), )) } else { std::task::Poll::Pending } } ExpiryCheck(call) => Pin::new(call).poll(cx).map(ApiResult::ExpiryCheck), } } } pub(crate) enum ApiResult { Login(Result, ResponseTx<()>), Rotation(Result), Validation(Result), VoucherSubmission( Result, ResponseTx, ), #[cfg(target_os = "android")] InitPlayPurchase( Result, ResponseTx, ), #[cfg(target_os = "android")] VerifyPlayPurchase(Result<(), Error>, ResponseTx<()>), ExpiryCheck(Result, Error>), }