use std::sync::{Arc, Mutex, PoisonError};
use crate::device_id::{DeviceIdDescription, DeviceIdResolver};
use crate::error::{Error, Result};
use crate::fingerprint_spec::{self, DeviceIdSource, Parameter};
use crate::platform;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DeviceIdentity {
pub params: Vec<Parameter>,
pub device_name: String,
}
pub type DeviceIdentityReader = Arc<dyn Fn() -> DeviceIdentity + Send + Sync>;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum DeviceIdFallback {
#[default]
None,
DeviceName,
}
#[derive(Clone, Default)]
pub struct MoonbaseDeviceIdResolverOptions {
pub fallback: DeviceIdFallback,
pub reader: Option<DeviceIdentityReader>,
pub platform: Option<String>,
}
impl std::fmt::Debug for MoonbaseDeviceIdResolverOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MoonbaseDeviceIdResolverOptions")
.field("fallback", &self.fallback)
.field("reader", &self.reader.as_ref().map(|_| "custom"))
.field("platform", &self.platform)
.finish()
}
}
#[derive(Default)]
struct Memo {
identity: Option<DeviceIdentity>,
description: Option<DeviceIdDescription>,
}
pub struct MoonbaseDeviceIdResolver {
options: MoonbaseDeviceIdResolverOptions,
platform: String,
memo: Mutex<Memo>,
}
impl Default for MoonbaseDeviceIdResolver {
fn default() -> Self {
Self::new()
}
}
impl MoonbaseDeviceIdResolver {
pub fn new() -> Self {
Self::with_options(MoonbaseDeviceIdResolverOptions::default())
}
pub fn with_options(options: MoonbaseDeviceIdResolverOptions) -> Self {
let platform = options.platform.clone().unwrap_or_else(|| fingerprint_spec::platform_tag().to_owned());
Self { options, platform, memo: Mutex::new(Memo::default()) }
}
pub fn read_host_identity() -> DeviceIdentity {
DeviceIdentity { params: platform::read_identity_params(), device_name: Self::read_host_name() }
}
pub fn read_host_name() -> String {
platform::read_host_name()
}
fn read(&self) -> DeviceIdentity {
match &self.options.reader {
Some(reader) => reader(),
None => Self::read_host_identity(),
}
}
fn describe(&self, params: &[Parameter], source: DeviceIdSource) -> Result<DeviceIdDescription> {
let material = fingerprint_spec::build_fingerprint_material(&self.platform, params)?;
Ok(DeviceIdDescription {
device_id: fingerprint_spec::fingerprint_device_id(&material, source),
version: fingerprint_spec::VERSION,
platform: self.platform.clone(),
source,
param_names: fingerprint_spec::canonicalize_params(params)?.into_iter().map(|(name, _)| name).collect(),
})
}
fn description(&self) -> Result<DeviceIdDescription> {
let mut memo = self.memo.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(description) = &memo.description {
return Ok(description.clone());
}
let identity = match memo.identity.take() {
Some(identity) => identity,
None => self.read(),
};
let source = match self.platform.as_str() {
"ios" | "android" => DeviceIdSource::Scoped,
_ => DeviceIdSource::Identity,
};
let described = match self.describe(&identity.params, source) {
Err(Error::InsufficientDeviceIdentity { .. }) if self.options.fallback == DeviceIdFallback::DeviceName => {
let fallback = [("deviceName".to_owned(), identity.device_name.clone())];
self.describe(&fallback, DeviceIdSource::DeviceName)
}
other => other,
};
match described {
Ok(description) => {
memo.identity = Some(identity);
memo.description = Some(description.clone());
Ok(description)
}
Err(error) => {
memo.identity = None;
Err(error)
}
}
}
}
impl DeviceIdResolver for MoonbaseDeviceIdResolver {
fn device_name(&self) -> String {
let mut memo = self.memo.lock().unwrap_or_else(PoisonError::into_inner);
if memo.identity.is_none() {
memo.identity = Some(self.read());
}
memo.identity.as_ref().map(|identity| identity.device_name.clone()).unwrap_or_default()
}
fn device_id(&self) -> Result<String> {
self.description().map(|description| description.device_id)
}
fn describe_device(&self) -> Result<Option<DeviceIdDescription>> {
self.description().map(Some)
}
}
impl std::fmt::Debug for MoonbaseDeviceIdResolver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MoonbaseDeviceIdResolver")
.field("platform", &self.platform)
.field("fallback", &self.options.fallback)
.finish_non_exhaustive()
}
}