""" Utility functions for DFIR iOS Enhancement Application """ from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any, Optional import hashlib import sqlite3 # Apple epoch (January 1, 2001) APPLE_EPOCH = datetime(2001, 1, 1, tzinfo=timezone.utc) def apple_time_to_datetime(ts: Any) -> Optional[datetime]: """ Convert Apple timestamp to Python datetime. Apple uses a custom epoch starting from January 1, 2001. Args: ts: Apple timestamp (can be seconds or nanoseconds) Returns: datetime object or None if conversion fails """ if ts is None: return None try: ts = float(ts) # Handle nanoseconds if ts > 1e12: ts = ts / 1e9 return APPLE_EPOCH + timedelta(seconds=ts) except Exception: return None def normalize_phone(phone: Optional[str]) -> Optional[str]: """ Normalize phone number by removing common formatting characters. Args: phone: Raw phone number string Returns: Normalized phone number or None """ if not phone: return phone value = str(phone).replace("+1", "").replace(" ", "").replace("-", "") value = value.replace("(", "").replace(")", "") return value.strip() or None def sha256_file(path: Path, chunk_size: int = 1024 * 1024) -> str: """ Calculate SHA256 hash of a file. Args: path: Path to the file chunk_size: Size of chunks to read (default 1MB) Returns: Hexadecimal SHA256 hash string """ h = hashlib.sha256() with path.open("rb") as f: while True: chunk = f.read(chunk_size) if not chunk: break h.update(chunk) return h.hexdigest() def get_manifest_files(manifest_db: Path) -> list: """ Query Manifest.db for file entries. Args: manifest_db: Path to Manifest.db Returns: List of file entries from manifest """ if not manifest_db.exists(): return [] conn = sqlite3.connect(str(manifest_db)) conn.row_factory = sqlite3.Row cursor = conn.cursor() try: cursor.execute(""" SELECT fileID, domain, relativePath, flags, file FROM Files WHERE relativePath IS NOT NULL """) return [dict(row) for row in cursor.fetchall()] except Exception: return [] finally: conn.close() def find_artifact_in_manifest(manifest_files: list, artifact_name: str) -> Optional[dict]: """ Find a specific artifact in manifest files. Args: manifest_files: List of manifest file entries artifact_name: Name of artifact to find Returns: File entry dict or None """ for file_entry in manifest_files: if artifact_name in (file_entry.get('relativePath') or ''): return file_entry return None