13 KiB
Device Profile Generation Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Build a local device profile generator that can create, persist, reload, and export stable synthetic Android device identities.
Architecture: Add a focused core/device_profile.py module for pure identity generation and validation. Add a small tools/new_device.py CLI that uses the core module without depending on APP runtime, Frida, HAR, or out/.
Tech Stack: Python 3.13, standard library only, pytest for tests.
File Structure
- Create
tests/test_device_profile.py- Unit tests for profile generation, validation, persistence, cloud override, and env export.
- Create
core/device_profile.py- Dataclass model, generator, validation helpers, JSON persistence, env export.
- Create
tools/new_device.py- CLI for generating one or more profiles.
- Modify
core/__init__.py- Export
DeviceProfile,DeviceProfileGenerator,load_device_profile, andsave_device_profile.
- Export
Task 1: Add failing tests for device profile core
Files:
-
Create:
tests/test_device_profile.py -
Create later:
core/device_profile.py -
Step 1: Write the failing tests
import hashlib
import json
import pytest
from core.device_profile import (
DeviceProfile,
DeviceProfileGenerator,
load_device_profile,
save_device_profile,
)
def test_generator_creates_consistent_local_identity():
profile = DeviceProfileGenerator(seed=1234).new_profile()
assert len(profile.android_id) == 16
assert profile.android_id == profile.android_id.lower()
int(profile.android_id, 16)
assert profile.o_did == f"ANDROID_{profile.android_id}"
assert profile.local_did.startswith("ANDROID_")
assert profile.did == profile.local_did
expected_rdid = hashlib.md5(profile.g_rdi2.encode("utf-8")).hexdigest()[16:32]
assert profile.rdid == f"ANDROID_{expected_rdid}"
def test_profile_persistence_roundtrip(tmp_path):
path = tmp_path / "device.json"
profile = DeviceProfileGenerator(seed=5678).new_profile()
save_device_profile(profile, path)
loaded = load_device_profile(path)
assert loaded == profile
assert json.loads(path.read_text(encoding="utf-8"))["android_id"] == profile.android_id
def test_apply_cloud_identity_updates_only_server_fields():
profile = DeviceProfileGenerator(seed=9012).new_profile()
old_android_id = profile.android_id
old_o_did = profile.o_did
old_rdid = profile.rdid
profile.apply_cloud_identity(
did="ANDROID_e8dfd2f16b618053",
cdid_tag=2,
egid="DFP68CA12B5D3C714E4439D5E255B197DA809D63CB77139F1420A763F53FE718",
)
assert profile.did == "ANDROID_e8dfd2f16b618053"
assert profile.cdid_tag == 2
assert profile.egid == "DFP68CA12B5D3C714E4439D5E255B197DA809D63CB77139F1420A763F53FE718"
assert profile.android_id == old_android_id
assert profile.o_did == old_o_did
assert profile.rdid == old_rdid
def test_env_export_contains_expected_identity_keys():
profile = DeviceProfileGenerator(seed=3456).new_profile()
env_text = profile.to_env()
assert f"KS_ANDROID_ID={profile.android_id}" in env_text
assert f"KS_DID={profile.did}" in env_text
assert f"KS_ODID={profile.o_did}" in env_text
assert f"KS_RDID={profile.rdid}" in env_text
assert f"KS_LOCAL_DID={profile.local_did}" in env_text
@pytest.mark.parametrize(
"field,value",
[
("android_id", "XYZ"),
("local_did", "BAD"),
("did", "BAD"),
("o_did", "BAD"),
("rdid", "BAD"),
],
)
def test_profile_validation_rejects_invalid_identity_fields(field, value):
data = DeviceProfileGenerator(seed=7890).new_profile().to_dict()
data[field] = value
with pytest.raises(ValueError):
DeviceProfile.from_dict(data)
- Step 2: Run test to verify it fails
Run:
uv run pytest tests/test_device_profile.py -v
Expected:
ModuleNotFoundError: No module named 'core.device_profile'
Task 2: Implement core device profile module
Files:
-
Create:
core/device_profile.py -
Modify:
core/__init__.py -
Test:
tests/test_device_profile.py -
Step 1: Create
core/device_profile.py
from __future__ import annotations
import hashlib
import json
import random
import re
import time
import uuid
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any
ANDROID_ID_RE = re.compile(r"^[0-9a-f]{16}$")
ANDROID_PREFIX_RE = re.compile(r"^ANDROID_[0-9a-f]{16}$")
EGID_RE = re.compile(r"^$|^DFP[0-9A-F]{61}$")
@dataclass
class DeviceProfile:
android_id: str
local_did: str
did: str
o_did: str
rdid: str
g_rdi2: str
cdid_tag: int = 0
egid: str = ""
install_time_ms: int = 0
cold_launch_time_ms: int = 0
sid: str = ""
def __post_init__(self) -> None:
self.validate()
def validate(self) -> None:
if not ANDROID_ID_RE.fullmatch(self.android_id):
raise ValueError(f"invalid android_id: {self.android_id!r}")
for name in ("local_did", "did", "o_did", "rdid"):
value = getattr(self, name)
if not ANDROID_PREFIX_RE.fullmatch(value):
raise ValueError(f"invalid {name}: {value!r}")
if self.o_did != f"ANDROID_{self.android_id}":
raise ValueError("o_did must equal ANDROID_<android_id>")
expected_rdid = hashlib.md5(self.g_rdi2.encode("utf-8")).hexdigest()[16:32]
if self.rdid != f"ANDROID_{expected_rdid}":
raise ValueError("rdid must equal ANDROID_<md5(g_rdi2)[16:32]>")
if not isinstance(self.cdid_tag, int) or self.cdid_tag < 0:
raise ValueError(f"invalid cdid_tag: {self.cdid_tag!r}")
if not EGID_RE.fullmatch(self.egid):
raise ValueError(f"invalid egid: {self.egid!r}")
def apply_cloud_identity(self, did: str, cdid_tag: int, egid: str = "") -> None:
self.did = did
self.cdid_tag = cdid_tag
if egid:
self.egid = egid
self.validate()
def to_dict(self) -> dict[str, Any]:
return asdict(self)
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "DeviceProfile":
return cls(
android_id=str(data["android_id"]),
local_did=str(data["local_did"]),
did=str(data["did"]),
o_did=str(data["o_did"]),
rdid=str(data["rdid"]),
g_rdi2=str(data["g_rdi2"]),
cdid_tag=int(data.get("cdid_tag", 0)),
egid=str(data.get("egid", "")),
install_time_ms=int(data.get("install_time_ms", 0)),
cold_launch_time_ms=int(data.get("cold_launch_time_ms", 0)),
sid=str(data.get("sid", "")),
)
def to_env(self) -> str:
lines = [
f"KS_ANDROID_ID={self.android_id}",
f"KS_DID={self.did}",
f"KS_LOCAL_DID={self.local_did}",
f"KS_ODID={self.o_did}",
f"KS_RDID={self.rdid}",
f"KS_GRDI2={self.g_rdi2}",
f"KS_CDID_TAG={self.cdid_tag}",
f"KS_EGID={self.egid}",
f"KS_INSTALL_TIME_MS={self.install_time_ms}",
f"KS_COLD_LAUNCH_TIME_MS={self.cold_launch_time_ms}",
f"KS_SID={self.sid}",
]
return "\n".join(lines) + "\n"
class DeviceProfileGenerator:
def __init__(self, seed: int | None = None) -> None:
self._random = random.Random(seed)
def new_profile(self) -> DeviceProfile:
now_ms = int(time.time() * 1000)
android_id = self._hex16()
local_did = f"ANDROID_{self._hex16()}"
g_rdi2 = self._g_rdi2()
rdid_suffix = hashlib.md5(g_rdi2.encode("utf-8")).hexdigest()[16:32]
return DeviceProfile(
android_id=android_id,
local_did=local_did,
did=local_did,
o_did=f"ANDROID_{android_id}",
rdid=f"ANDROID_{rdid_suffix}",
g_rdi2=g_rdi2,
install_time_ms=now_ms - self._random.randint(10_000, 600_000),
cold_launch_time_ms=now_ms,
sid=str(uuid.UUID(int=self._random.getrandbits(128))),
)
def _hex16(self) -> str:
return f"{self._random.getrandbits(64):016x}"
def _g_rdi2(self) -> str:
parts = []
for _ in range(5):
left = self._random.choice((7, 8, 9)) * 100_000_000 + self._random.randint(0, 999_999)
right = self._random.choice((4741, 8641))
parts.append(f"{left}::{right}")
return "|".join(parts)
def save_device_profile(profile: DeviceProfile, path: str | Path) -> None:
target = Path(path)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(
json.dumps(profile.to_dict(), ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
def load_device_profile(path: str | Path) -> DeviceProfile:
source = Path(path)
try:
data = json.loads(source.read_text(encoding="utf-8"))
except Exception as exc:
raise ValueError(f"failed to load device profile: {source}") from exc
if not isinstance(data, dict):
raise ValueError(f"device profile must be a JSON object: {source}")
return DeviceProfile.from_dict(data)
- Step 2: Export from
core/__init__.py
from .device_profile import (
DeviceProfile,
DeviceProfileGenerator,
load_device_profile,
save_device_profile,
)
__all__ = [
"DeviceProfile",
"DeviceProfileGenerator",
"load_device_profile",
"save_device_profile",
]
- Step 3: Run tests
Run:
uv run pytest tests/test_device_profile.py -v
Expected:
8 passed
Task 3: Add CLI for generating profiles
Files:
-
Create:
tools/new_device.py -
Test with smoke commands.
-
Step 1: Create
tools/new_device.py
from __future__ import annotations
import argparse
from pathlib import Path
from core.device_profile import DeviceProfileGenerator, save_device_profile
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Generate local Kuaishou device profiles")
parser.add_argument("--count", type=int, default=1)
parser.add_argument("--out-dir", default="out/devices")
parser.add_argument("--prefix", default="device")
parser.add_argument("--seed", type=int, default=None)
parser.add_argument("--env", action="store_true", help="also write .env files")
parser.add_argument("--force", action="store_true", help="overwrite existing files")
return parser
def main() -> int:
args = build_parser().parse_args()
if args.count < 1:
raise SystemExit("--count must be >= 1")
out_dir = Path(args.out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
generator = DeviceProfileGenerator(seed=args.seed)
for index in range(1, args.count + 1):
profile = generator.new_profile()
stem = f"{args.prefix}_{index:03d}"
json_path = out_dir / f"{stem}.json"
env_path = out_dir / f"{stem}.env"
if not args.force and json_path.exists():
raise SystemExit(f"refusing to overwrite existing file: {json_path}")
if args.env and not args.force and env_path.exists():
raise SystemExit(f"refusing to overwrite existing file: {env_path}")
save_device_profile(profile, json_path)
if args.env:
env_path.write_text(profile.to_env(), encoding="utf-8")
print(f"[OK] {json_path} did={profile.did} odid={profile.o_did} rdid={profile.rdid}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
- Step 2: Run CLI smoke test
Run:
uv run python tools/new_device.py --count 2 --out-dir out/devices_test --seed 1 --env --force
Expected:
[OK] out\devices_test\device_001.json ...
[OK] out\devices_test\device_002.json ...
- Step 3: Compile check
Run:
uv run python -m compileall core tools tests
Expected:
Listing 'core'...
Listing 'tools'...
Listing 'tests'...
Task 4: Final verification
Files:
-
Verify:
core/device_profile.py -
Verify:
tools/new_device.py -
Verify:
tests/test_device_profile.py -
Step 1: Run focused tests
Run:
uv run pytest tests/test_device_profile.py -v
Expected:
8 passed
- Step 2: Run existing test suite
Run:
uv run pytest tests -v
Expected:
all tests passed
- Step 3: Generate sample profile
Run:
uv run python tools/new_device.py --count 1 --out-dir out/devices_sample --seed 20260711 --env --force
Expected:
[OK] out\devices_sample\device_001.json did=ANDROID_...
Generated files:
out/devices_sample/device_001.json
out/devices_sample/device_001.env
Self-Review
- Spec coverage: local generation, persistence, env export, cloud override, and validation are covered.
- Placeholder scan: no
TBD,TODO, or unspecified code steps remain. - Type consistency:
DeviceProfile,DeviceProfileGenerator,save_device_profile, andload_device_profilenames match across all tasks. - Scope check: online DFP bootstrap remains out of phase 1 by design.