use crate::models::DeviceProfile;
use rand::Rng;
use serde_json::Value;
use std::fs;
use std::path::{Path, PathBuf};
use uuid::Uuid;
const DATA_DIR: &str = ".gephyr";
const GLOBAL_BASELINE: &str = "device_original.json";
const STORAGE_JSON_PATH_ENV: &str = "ANTIGRAVITY_STORAGE_JSON_PATH";
fn get_data_dir() -> Result<PathBuf, String> {
let home = dirs::home_dir().ok_or("failed_to_get_home_dir")?;
let data_dir = home.join(DATA_DIR);
if !data_dir.exists() {
fs::create_dir_all(&data_dir).map_err(|e| format!("failed_to_create_data_dir: {}", e))?;
}
Ok(data_dir)
}
pub fn get_storage_path() -> Result<PathBuf, String> {
if let Ok(path) = std::env::var(STORAGE_JSON_PATH_ENV) {
let trimmed = path.trim();
if !trimmed.is_empty() {
let storage = PathBuf::from(trimmed);
if storage.exists() {
return Ok(storage);
}
return Err("storage_json_override_not_found".to_string());
}
}
Err("storage_json_auto_discovery_disabled".to_string())
}
pub fn read_profile(storage_path: &Path) -> Result<DeviceProfile, String> {
let content = fs::read_to_string(storage_path)
.map_err(|e| format!("read_failed ({:?}): {}", storage_path, e))?;
let json: Value = serde_json::from_str(&content)
.map_err(|e| format!("parse_failed ({:?}): {}", storage_path, e))?;
let get_field = |key: &str| -> Option<String> {
if let Some(obj) = json.get("telemetry").and_then(|v| v.as_object()) {
if let Some(v) = obj.get(key).and_then(|v| v.as_str()) {
return Some(v.to_string());
}
}
if let Some(v) = json
.get(format!("telemetry.{key}"))
.and_then(|v| v.as_str())
{
return Some(v.to_string());
}
None
};
let machine_id = get_field("machineId").ok_or("missing_machine_id")?;
Ok(DeviceProfile {
machine_id: Some(machine_id),
mac_machine_id: get_field("macMachineId"),
dev_device_id: get_field("devDeviceId"),
sqm_id: get_field("sqmId"),
})
}
pub fn load_global_original() -> Option<DeviceProfile> {
if let Ok(dir) = get_data_dir() {
let path = dir.join(GLOBAL_BASELINE);
if path.exists() {
if let Ok(content) = fs::read_to_string(&path) {
if let Ok(profile) = serde_json::from_str::<DeviceProfile>(&content) {
return Some(profile);
}
}
}
}
None
}
pub fn save_global_original(profile: &DeviceProfile) -> Result<(), String> {
let dir = get_data_dir()?;
let path = dir.join(GLOBAL_BASELINE);
if path.exists() {
return Ok(());
}
let content =
serde_json::to_string_pretty(profile).map_err(|e| format!("serialize_failed: {}", e))?;
fs::write(&path, content).map_err(|e| format!("write_failed: {}", e))
}
pub fn generate_profile() -> DeviceProfile {
DeviceProfile {
machine_id: Some(generate_machine_id()),
mac_machine_id: Some(new_standard_machine_id()),
dev_device_id: Some(Uuid::new_v4().to_string()),
sqm_id: Some(format!("{{{}}}", Uuid::new_v4().to_string().to_uppercase())),
}
}
pub fn generate_machine_id() -> String {
random_hex(64)
}
pub fn is_valid_machine_id(value: &str) -> bool {
if value.len() != 64 {
return false;
}
value
.as_bytes()
.iter()
.all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
}
pub fn normalize_machine_id(value: &str) -> Option<String> {
if value.starts_with("auth0|user_") {
return Some(generate_machine_id());
}
if is_valid_machine_id(value) {
None
} else {
Some(generate_machine_id())
}
}
fn random_hex(length: usize) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut rng = rand::thread_rng();
let mut out = String::with_capacity(length);
for _ in 0..length {
let idx = rng.gen_range(0..16);
out.push(HEX[idx] as char);
}
out
}
fn new_standard_machine_id() -> String {
let mut rng = rand::thread_rng();
let mut id = String::with_capacity(36);
for ch in "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".chars() {
if ch == '-' || ch == '4' {
id.push(ch);
} else if ch == 'x' {
id.push_str(&format!("{:x}", rng.gen_range(0..16)));
} else if ch == 'y' {
id.push_str(&format!("{:x}", rng.gen_range(8..12)));
}
}
id
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn random_hex_is_hex_only_and_correct_length() {
let s = random_hex(64);
assert_eq!(s.len(), 64);
assert!(s
.as_bytes()
.iter()
.all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f')));
}
#[test]
fn generate_profile_machine_id_is_valid() {
let p = generate_profile();
let id = p.machine_id.expect("generated machine_id");
assert!(is_valid_machine_id(&id));
}
#[test]
fn normalize_machine_id_migrates_auth0_prefix() {
let migrated = normalize_machine_id("auth0|user_deadbeef").expect("should migrate");
assert!(is_valid_machine_id(&migrated));
}
}