ksjsb/docs/superpowers/plans/2026-07-11-device-profile-generation.md
2026-07-30 20:25:56 +08:00

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, and save_device_profile.

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, and load_device_profile names match across all tasks.
  • Scope check: online DFP bootstrap remains out of phase 1 by design.