use crate::{
DeviceProperties,
api::{endpoint::Requester, form::Form},
consts,
error::{
CheckinError, FetchAuthError, IntoGoogleError, PropsError, TocError, UploadDeviceError,
},
proto,
};
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use prost::Message;
use std::{
borrow::Cow,
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
pub async fn checkin(
props: &DeviceProperties,
) -> Result<proto::AndroidCheckinResponse, CheckinError> {
let mut request = proto::AndroidCheckinRequest {
device_configuration: Some(props.device_config.clone()),
checkin: Some(props.android_checkin.clone()),
time_zone: Some(consts::TIMEZONE.to_string()),
locale: Some(consts::LOCALE.to_string()),
version: Some(3),
fragment: Some(0),
id: Some(0),
..Default::default()
};
let body = {
let checkin = request.checkin.as_mut().ok_or(CheckinError::NoDevice())?; let build = checkin.build.as_mut().ok_or(CheckinError::NoBuild())?;
let seconds_unix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
build.timestamp = Some(seconds_unix.cast_signed());
request.encode_to_vec()
};
let headers = [
("content-type", "application/x-protobuf"),
("host", "android.clients.google.com"),
];
let request = Requester::new()
.props(props)
.headers(&headers)
.google_auth_headers()?;
let response = request.generic_request("checkin", Some(body)).await?;
let bytes = response.bytes().await?;
let response = proto::AndroidCheckinResponse::decode(bytes)?;
Ok(response)
}
pub async fn upload_device(
props: &DeviceProperties,
gsf: i64,
checkin_token: &str,
) -> Result<proto::UploadDeviceConfigResponse, UploadDeviceError> {
let request = proto::UploadDeviceConfigRequest {
device_configuration: Some(props.device_config.clone()),
..Default::default()
};
let body = request.encode_to_vec();
let headers = [("content-type", "application/x-protobuf")];
let request = Requester::new()
.props(props)
.gsf(gsf)
.checkin_token(checkin_token)
.headers(&headers)
.default_headers()
.await?;
let mut wrapper = request
.fdfe_request("uploadDeviceConfig", Some(body))
.await?;
let response = wrapper
.payload
.take()
.and_then(|a| a.upload_device_config_response)
.into_google_error(&wrapper)?;
Ok(response)
}
pub struct AuthToken(RwLock<InnerAuth>);
struct InnerAuth {
token: String,
expire: Instant,
}
impl AuthToken {
#[must_use]
pub const fn new(token: String, expire: Instant) -> Self {
Self(RwLock::new(InnerAuth { token, expire }))
}
#[must_use]
pub fn get(&'_ self) -> Option<MappedRwLockReadGuard<'_, str>> {
let read_guard = self.0.read();
if Instant::now() >= read_guard.expire {
None
} else {
Some(RwLockReadGuard::map(read_guard, |a| &*a.token))
}
}
pub fn update(&self, other: &Self) {
let mut write_guard = self.0.write();
let mut other_read = other.0.write();
write_guard.token = std::mem::take(&mut other_read.token);
write_guard.expire = std::mem::replace(&mut other_read.expire, Instant::now());
}
}
pub async fn fetch_auth(
props: &DeviceProperties,
aas_token: &str,
email: &str,
gsf: i64,
) -> Result<AuthToken, FetchAuthError> {
let build = props
.android_checkin
.build
.as_ref()
.ok_or(FetchAuthError::Props(PropsError::NoBuild()))?;
let sdk_version = build
.sdk_version
.ok_or(FetchAuthError::Props(PropsError::NoBuildSDK()))?
.to_string();
let services_version = build
.google_services
.ok_or(FetchAuthError::Props(PropsError::NoVersion()))?
.to_string();
let language = consts::LANGUAGE.to_ascii_lowercase();
let gsf = format!("{gsf:x}");
let params = [
(
"service",
"oauth2:https://www.googleapis.com/auth/googleplay",
),
("google_play_services_version", &services_version),
("device_country", consts::COUNTRY_CODE),
("callerPkg", consts::ANDROID_VENDING),
("client_sig", consts::CLIENT_SIG),
("app", consts::ANDROID_VENDING2),
("lang", &language),
("callerSig", consts::CALLER_SIG),
("token_request_options", "CAA4AVAB"),
("sdk_version", &sdk_version),
("oauth2_foreground", "1"),
("system_partition", "1"),
("androidId", &gsf),
("check_email", "1"),
("Token", aas_token),
("Email", email),
];
let headers = [("content-length", "0")];
let request = Requester::new()
.props(props)
.aas_token(aas_token)
.email(email)
.params(¶ms)
.headers(&headers)
.google_auth_headers()?;
let mut response = request.form_request("auth", Form::new([])).await?;
let token = response.remove("Auth").into_google_error_map(|| {
response
.remove("Error")
.unwrap_or(Cow::Borrowed("Google returned no error!"))
.to_string()
})?;
let secs = response
.remove("ExpiresInDurationSec")
.map(|a| a.parse::<u64>())
.ok_or(FetchAuthError::NoExpireTime())??;
Ok(AuthToken::new(
token.to_string(),
Instant::now() + Duration::from_secs(secs),
))
}
pub async fn toc(
auth_token: &AuthToken,
gsf: i64,
checkin_token: &str,
props: &DeviceProperties,
) -> Result<String, TocError> {
let request = Requester::new()
.auth_token(auth_token)
.gsf(gsf)
.checkin_token(checkin_token)
.props(props)
.default_headers()
.await?;
let mut wrapper = request.fdfe_request("toc", None).await?;
let toc_response = wrapper
.payload
.take()
.and_then(|a| a.toc_response)
.into_google_error(&wrapper)?;
if let Some(token) = toc_response.tos_token {
let form = Form::new([("tost", &token), ("toscme", "false")]);
let mut tos_wrapper = request
.fdfe_request("acceptTos", Some(form.format().into_bytes()))
.await?;
tos_wrapper
.payload
.take()
.and_then(|a| a.accept_tos_response)
.into_google_error(&tos_wrapper)?;
}
let response = toc_response.cookie.into_google_error(&wrapper)?;
Ok(response)
}