569 lines
22 KiB
Python
569 lines
22 KiB
Python
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import json
|
||
import random
|
||
import re
|
||
import time
|
||
import uuid
|
||
from dataclasses import asdict, dataclass, field
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from .device_id import egid_from_seed_material, is_valid_oaid, oaid_from_seed_material
|
||
|
||
|
||
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 DfpRuntimeHints:
|
||
did_gt: str = ""
|
||
total_memory_bytes: str = ""
|
||
build_fingerprint: str = ""
|
||
build_product: str = ""
|
||
k4_native: str = ""
|
||
storage_available_bytes: int = 0
|
||
k51_native: str = ""
|
||
k84_native: str = ""
|
||
res_soc: str = ""
|
||
boot_id: str = ""
|
||
grdi: str = ""
|
||
ipv6_map: str = ""
|
||
lpss: str = ""
|
||
keeper_seed: str = ""
|
||
du: str = ""
|
||
sted_cache_json: str = ""
|
||
persisted_cache_m: str = ""
|
||
manus: str = ""
|
||
gaid: str = ""
|
||
oaid: str = ""
|
||
wifi_mac: str = ""
|
||
|
||
@classmethod
|
||
def from_dict(cls, data: dict[str, Any] | None) -> "DfpRuntimeHints":
|
||
data = data or {}
|
||
return cls(
|
||
did_gt=str(data.get("did_gt", "")),
|
||
total_memory_bytes=str(data.get("total_memory_bytes", "")),
|
||
build_fingerprint=str(data.get("build_fingerprint", "")),
|
||
build_product=str(data.get("build_product", "")),
|
||
k4_native=str(data.get("k4_native", "")),
|
||
storage_available_bytes=int(data.get("storage_available_bytes", 0)),
|
||
k51_native=str(data.get("k51_native", "")),
|
||
k84_native=str(data.get("k84_native", "")),
|
||
res_soc=str(data.get("res_soc", "")),
|
||
boot_id=str(data.get("boot_id", "")),
|
||
grdi=str(data.get("grdi", "")),
|
||
ipv6_map=str(data.get("ipv6_map", "")),
|
||
lpss=str(data.get("lpss", "")),
|
||
keeper_seed=str(data.get("keeper_seed", "")),
|
||
du=str(data.get("du", "")),
|
||
sted_cache_json=str(data.get("sted_cache_json", data.get("cache_m", ""))),
|
||
persisted_cache_m=str(data.get("persisted_cache_m", "")),
|
||
manus=str(data.get("manus", "")),
|
||
gaid=str(data.get("gaid", "")),
|
||
oaid=str(data.get("oaid", "")),
|
||
wifi_mac=str(data.get("wifi_mac", "")),
|
||
)
|
||
|
||
|
||
@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 = ""
|
||
package_name: str = "com.kuaishou.nebula"
|
||
app_version: str = "14.5.50.11631"
|
||
android_release: str = "16"
|
||
manufacturer: str = "OnePlus"
|
||
brand: str = "OPPO"
|
||
model: str = "PJZ110"
|
||
build_id: str = "BP2A.250605.015"
|
||
build_display: str = "PJZ110_16.0.8.301(CN01)"
|
||
build_product: str = "OP5D0DL1"
|
||
build_fingerprint: str = ""
|
||
build_tags: str = "release-keys"
|
||
build_type: str = "user"
|
||
board_platform: str = "sun"
|
||
soc_name: str = "Qualcomm Snapdragon 8750"
|
||
max_memory: int = 256
|
||
device_bit: str = "4"
|
||
screen_width: int = 1080
|
||
screen_height: int = 2376
|
||
status_bar_height: int = 120
|
||
screen_density: float = 3.0
|
||
screen_xdpi: str = "386.36618"
|
||
screen_ydpi: str = "381.96454"
|
||
total_memory_mb: int = 15107
|
||
country_code: str = "cn"
|
||
isp: str = "CUCC"
|
||
runtime_hints: DfpRuntimeHints = field(default_factory=DfpRuntimeHints)
|
||
|
||
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}")
|
||
if self.runtime_hints.oaid and not is_valid_oaid(self.runtime_hints.oaid):
|
||
raise ValueError(f"invalid oaid: {self.runtime_hints.oaid!r}")
|
||
if self.screen_width <= 0 or self.screen_height <= 0:
|
||
raise ValueError("screen size must be positive")
|
||
if self.total_memory_mb <= 0:
|
||
raise ValueError("total_memory_mb must be positive")
|
||
if self.max_memory <= 0:
|
||
raise ValueError("max_memory must be positive")
|
||
if not self.board_platform:
|
||
raise ValueError("board_platform must not be empty")
|
||
if not self.soc_name:
|
||
raise ValueError("soc_name must not be empty")
|
||
|
||
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.sync_egid_cache()
|
||
self.validate()
|
||
|
||
def sync_egid_cache(self) -> None:
|
||
"""让 k112/STED JSON 与公开 EGID 保持同一设备身份。"""
|
||
if not self.egid:
|
||
return
|
||
kuaishou_egid = egid_from_seed_material(["KUAISHOU", self.egid, self.android_id])
|
||
self.runtime_hints.sted_cache_json = json.dumps(
|
||
{"NEBULA": self.egid, "KUAISHOU": kuaishou_egid},
|
||
separators=(",", ":"),
|
||
)
|
||
self.refresh_persisted_cache_m()
|
||
|
||
def refresh_persisted_cache_m(self) -> None:
|
||
"""刷新 rq0.d.cache_m,即 c.s(Context) 的本地硬件摘要。"""
|
||
from .dfp_cache import build_persisted_cache_m
|
||
|
||
self.runtime_hints.persisted_cache_m = build_persisted_cache_m(self)
|
||
|
||
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", "")),
|
||
package_name=str(data.get("package_name", "com.kuaishou.nebula")),
|
||
app_version=str(data.get("app_version", "14.5.50.11631")),
|
||
android_release=str(data.get("android_release", "16")),
|
||
manufacturer=str(data.get("manufacturer", "OnePlus")),
|
||
brand=str(data.get("brand", "OPPO")),
|
||
model=str(data.get("model", "PJZ110")),
|
||
build_id=str(data.get("build_id", "BP2A.250605.015")),
|
||
build_display=str(data.get("build_display", "PJZ110_16.0.8.301(CN01)")),
|
||
build_product=str(data.get("build_product", "OP5D0DL1")),
|
||
build_fingerprint=str(data.get("build_fingerprint", "")),
|
||
build_tags=str(data.get("build_tags", "release-keys")),
|
||
build_type=str(data.get("build_type", "user")),
|
||
board_platform=str(data.get("board_platform", "sun")),
|
||
soc_name=str(data.get("soc_name", "Qualcomm Snapdragon 8750")),
|
||
max_memory=int(data.get("max_memory", 256)),
|
||
device_bit=str(data.get("device_bit", "4")),
|
||
screen_width=int(data.get("screen_width", 1080)),
|
||
screen_height=int(data.get("screen_height", 2376)),
|
||
status_bar_height=int(data.get("status_bar_height", 120)),
|
||
screen_density=float(data.get("screen_density", 3.0)),
|
||
screen_xdpi=str(data.get("screen_xdpi", "386.36618")),
|
||
screen_ydpi=str(data.get("screen_ydpi", "381.96454")),
|
||
total_memory_mb=int(data.get("total_memory_mb", 15107)),
|
||
country_code=str(data.get("country_code", "cn")),
|
||
isp=str(data.get("isp", "CUCC")),
|
||
runtime_hints=DfpRuntimeHints.from_dict(data.get("runtime_hints")),
|
||
)
|
||
|
||
@property
|
||
def screen_metrics(self) -> str:
|
||
content_height = self.screen_height - self.status_bar_height - 48
|
||
return (
|
||
f"[{self.screen_density:.1f},{self.screen_width},{content_height},"
|
||
f"{self.screen_density:.1f},{self.screen_xdpi},{self.screen_ydpi}]"
|
||
)
|
||
|
||
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}",
|
||
f"KS_PACKAGE_NAME={self.package_name}",
|
||
f"KS_APPVER={self.app_version}",
|
||
f"KS_ANDROID_RELEASE={self.android_release}",
|
||
f"KS_MANUFACTURER={self.manufacturer}",
|
||
f"KS_BRAND={self.brand}",
|
||
f"KS_MODEL={self.model}",
|
||
f"KS_BUILD_ID={self.build_id}",
|
||
f"KS_BUILD_DISPLAY={self.build_display}",
|
||
f"KS_BUILD_PRODUCT={self.build_product}",
|
||
f"KS_BUILD_FINGERPRINT={self.build_fingerprint}",
|
||
f"KS_BUILD_TAGS={self.build_tags}",
|
||
f"KS_BUILD_TYPE={self.build_type}",
|
||
f"KS_BOARD_PLATFORM={self.board_platform}",
|
||
f"KS_SOC_NAME={self.soc_name}",
|
||
f"KS_MAX_MEMORY={self.max_memory}",
|
||
f"KS_DEVICE_BIT={self.device_bit}",
|
||
f"KS_SCREEN_WIDTH={self.screen_width}",
|
||
f"KS_SCREEN_HEIGHT={self.screen_height}",
|
||
f"KS_STATUS_BAR_HEIGHT={self.status_bar_height}",
|
||
f"KS_SCREEN_DENSITY={self.screen_density:.1f}",
|
||
f"KS_SCREEN_XDPI={self.screen_xdpi}",
|
||
f"KS_SCREEN_YDPI={self.screen_ydpi}",
|
||
f"KS_TOTAL_MEMORY_MB={self.total_memory_mb}",
|
||
f"KS_COUNTRY_CODE={self.country_code}",
|
||
f"KS_ISP={self.isp}",
|
||
f"KS_DID_GT={self.runtime_hints.did_gt}",
|
||
f"KS_TOTAL_MEMORY_BYTES={self.runtime_hints.total_memory_bytes}",
|
||
f"KS_K4={self.runtime_hints.k4_native}",
|
||
f"KS_K20={self.runtime_hints.storage_available_bytes}",
|
||
f"KS_K51={self.runtime_hints.k51_native}",
|
||
f"KS_K84={self.runtime_hints.k84_native}",
|
||
f"KS_RESSOC={self.runtime_hints.res_soc}",
|
||
f"KS_BOOT_ID={self.runtime_hints.boot_id}",
|
||
f"KS_K105={self.runtime_hints.grdi}",
|
||
f"KS_GRDI={self.runtime_hints.grdi}",
|
||
f"KS_IPV6_MAP={self.runtime_hints.ipv6_map}",
|
||
f"KS_LPSS={self.runtime_hints.lpss}",
|
||
f"KS_KEEPER_SEED={self.runtime_hints.keeper_seed}",
|
||
f"KS_DU={self.runtime_hints.du}",
|
||
f"KS_STED={self.runtime_hints.sted_cache_json}",
|
||
f"KS_DFP_CACHE_M={self.runtime_hints.persisted_cache_m}",
|
||
f"KS_MANUS={self.runtime_hints.manus}",
|
||
f"KS_GAID={self.runtime_hints.gaid}",
|
||
f"KS_OAID={self.runtime_hints.oaid}",
|
||
f"KS_WIFI_MAC={self.runtime_hints.wifi_mac}",
|
||
]
|
||
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)
|
||
install_time_ms = now_ms - self._random.randint(10_000, 600_000)
|
||
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]
|
||
hardware = self._hardware_profile()
|
||
total_memory_bytes = str(
|
||
hardware.get("total_memory_bytes", int(hardware["total_memory_mb"]) * 1024 * 1024)
|
||
)
|
||
hardware.pop("total_memory_bytes", None)
|
||
sid = str(uuid.UUID(int=self._random.getrandbits(128)))
|
||
egid = self._egid(android_id, local_did, g_rdi2, install_time_ms, now_ms, sid, hardware)
|
||
rdid = f"ANDROID_{rdid_suffix}"
|
||
oaid = oaid_from_seed_material([android_id, local_did, rdid, g_rdi2, sid, egid])
|
||
|
||
profile = DeviceProfile(
|
||
android_id=android_id,
|
||
local_did=local_did,
|
||
did=local_did,
|
||
o_did=f"ANDROID_{android_id}",
|
||
rdid=rdid,
|
||
g_rdi2=g_rdi2,
|
||
egid=egid,
|
||
install_time_ms=install_time_ms,
|
||
cold_launch_time_ms=now_ms,
|
||
sid=sid,
|
||
runtime_hints=self._runtime_hints(
|
||
egid,
|
||
did_gt=str(install_time_ms + self._random.randint(1_000, 60_000)),
|
||
total_memory_bytes=total_memory_bytes,
|
||
build_fingerprint=str(hardware.get("build_fingerprint", "")),
|
||
build_product=str(hardware.get("build_product", "")),
|
||
oaid=oaid,
|
||
),
|
||
**hardware,
|
||
)
|
||
profile.sync_egid_cache()
|
||
return profile
|
||
|
||
def _hex16(self) -> str:
|
||
return f"{self._random.getrandbits(64):016x}"
|
||
|
||
def _random_mac(self) -> str:
|
||
"""随机本地管理 MAC(首字节 bit1=1 locally administered, bit0=0 unicast)。"""
|
||
b = [0x02 | (self._random.getrandbits(6) << 2)]
|
||
b += [self._random.getrandbits(8) for _ in range(5)]
|
||
return ":".join(f"{x:02x}" for x in b)
|
||
|
||
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 _egid(
|
||
self,
|
||
android_id: str,
|
||
local_did: str,
|
||
g_rdi2: str,
|
||
install_time_ms: int,
|
||
cold_launch_time_ms: int,
|
||
sid: str,
|
||
hardware: dict[str, Any],
|
||
) -> str:
|
||
return egid_from_seed_material(
|
||
[
|
||
android_id,
|
||
local_did,
|
||
g_rdi2,
|
||
install_time_ms,
|
||
cold_launch_time_ms,
|
||
sid,
|
||
hardware.get("manufacturer", ""),
|
||
hardware.get("brand", ""),
|
||
hardware.get("model", ""),
|
||
hardware.get("build_id", ""),
|
||
hardware.get("screen_width", ""),
|
||
hardware.get("screen_height", ""),
|
||
hardware.get("total_memory_mb", ""),
|
||
]
|
||
)
|
||
|
||
def _hardware_profile(self) -> dict[str, Any]:
|
||
templates = [
|
||
{
|
||
"manufacturer": "OnePlus",
|
||
"brand": "OPPO",
|
||
"model": "PJZ110",
|
||
"build_id": "BP2A.250605.015",
|
||
"build_display": "PJZ110_16.0.8.301(CN01)",
|
||
"build_product": "OP5D0DL1",
|
||
"build_fingerprint": "OnePlus/PJZ110/OP5D0DL1:16/BP2A.250605.015/V.4e5c566-2a38f4c-2a4ca91:user/release-keys",
|
||
"board_platform": "sun",
|
||
"soc_name": "Qualcomm Snapdragon 8750",
|
||
"max_memory": 256,
|
||
"device_bit": "4",
|
||
"screen_width": 1080,
|
||
"screen_height": 2376,
|
||
"status_bar_height": 120,
|
||
"screen_density": 3.0,
|
||
"screen_xdpi": "386.36618",
|
||
"screen_ydpi": "381.96454",
|
||
"total_memory_mb": 15107,
|
||
"total_memory_bytes": 15841333248,
|
||
},
|
||
{
|
||
"manufacturer": "OPPO",
|
||
"brand": "OPPO",
|
||
"model": "PKB110",
|
||
"build_id": "BP1A.250305.019",
|
||
"build_display": "PKB110_15.0.1.601(CN01)",
|
||
"build_product": "PKB110",
|
||
"build_fingerprint": "OPPO/PKB110/PKB110:15/BP1A.250305.019/PKB110_15.0.1.601(CN01):user/release-keys",
|
||
"board_platform": "pineapple",
|
||
"soc_name": "Qualcomm Snapdragon 8 Gen 3",
|
||
"max_memory": 256,
|
||
"device_bit": "4",
|
||
"screen_width": 1080,
|
||
"screen_height": 2412,
|
||
"status_bar_height": 120,
|
||
"screen_density": 3.0,
|
||
"screen_xdpi": "394.215",
|
||
"screen_ydpi": "392.781",
|
||
"total_memory_mb": 12288,
|
||
"total_memory_bytes": 12884901888,
|
||
},
|
||
{
|
||
"manufacturer": "vivo",
|
||
"brand": "vivo",
|
||
"model": "V2408A",
|
||
"build_id": "BP1A.250205.007",
|
||
"build_display": "V2408A_A_15.1.9.6.W10",
|
||
"build_product": "V2408A",
|
||
"build_fingerprint": "vivo/V2408A/V2408A:15/BP1A.250205.007/V2408A_A_15.1.9.6.W10:user/release-keys",
|
||
"board_platform": "dimensity9400",
|
||
"soc_name": "MediaTek Dimensity 9400",
|
||
"max_memory": 256,
|
||
"device_bit": "4",
|
||
"screen_width": 1260,
|
||
"screen_height": 2800,
|
||
"status_bar_height": 132,
|
||
"screen_density": 3.0,
|
||
"screen_xdpi": "450.0",
|
||
"screen_ydpi": "450.0",
|
||
"total_memory_mb": 16384,
|
||
"total_memory_bytes": 17179869184,
|
||
},
|
||
]
|
||
selected = dict(self._random.choice(templates))
|
||
selected["isp"] = self._random.choice(["CUCC", "CTCC", "CMCC"])
|
||
return selected
|
||
|
||
def _runtime_hints(
|
||
self,
|
||
egid: str = "",
|
||
*,
|
||
did_gt: str = "",
|
||
total_memory_bytes: str = "",
|
||
build_fingerprint: str = "",
|
||
build_product: str = "",
|
||
oaid: str = "",
|
||
) -> DfpRuntimeHints:
|
||
storage_gb = self._random.randint(180, 460)
|
||
grdi_parts = []
|
||
for _ in range(5):
|
||
left = self._random.choice((5, 6, 7, 8, 9)) * 100_000_000 + self._random.randint(0, 99_999_999)
|
||
right = self._random.choice((3841, 4741, 5317, 8641))
|
||
grdi_parts.append(f"{left}::{right}")
|
||
|
||
ipv6_map = json.dumps(
|
||
self._ipv6_map(),
|
||
separators=(",", ":"),
|
||
)
|
||
manus = json.dumps(
|
||
{
|
||
"5": {
|
||
"1": "KWE_N",
|
||
"2": "KWE_N",
|
||
"3": "KWE_N",
|
||
"7": str(int(time.time() * 1000)),
|
||
"8": "KWE_N",
|
||
"10": "KWE_N",
|
||
}
|
||
},
|
||
separators=(",", ":"),
|
||
)
|
||
return DfpRuntimeHints(
|
||
did_gt=did_gt,
|
||
total_memory_bytes=total_memory_bytes,
|
||
build_fingerprint=build_fingerprint,
|
||
build_product=build_product,
|
||
k4_native=str(self._random.randint(1_000_000_000, 4_200_000_000)),
|
||
storage_available_bytes=storage_gb * 1024 * 1024 * 1024,
|
||
k51_native=self._hex16(),
|
||
k84_native=self._hex16(),
|
||
res_soc=f"soc-{self._hex16()}",
|
||
boot_id=str(uuid.UUID(int=self._random.getrandbits(128))),
|
||
grdi="|".join(grdi_parts),
|
||
ipv6_map=ipv6_map,
|
||
lpss=f"lp-{self._hex16()}",
|
||
keeper_seed=str(self._random.getrandbits(63)),
|
||
du=f"2@{self._hex16()}{self._hex16()}",
|
||
sted_cache_json=json.dumps({"NEBULA": egid}, separators=(",", ":")) if egid else "",
|
||
manus=manus,
|
||
gaid=str(uuid.UUID(int=self._random.getrandbits(128))),
|
||
oaid=oaid or (self._hex16() + self._hex16() + self._hex16() + self._hex16()).upper(),
|
||
wifi_mac=self._random_mac(),
|
||
)
|
||
|
||
def _ipv6_group(self) -> str:
|
||
return f"{self._random.getrandbits(16):x}"
|
||
|
||
def _ipv6_addr(self, prefix: str = "2408") -> str:
|
||
return (
|
||
f"{prefix}:{self._ipv6_group()}:{self._ipv6_group()}:{self._ipv6_group()}:"
|
||
f"{self._ipv6_group()}:{self._ipv6_group()}:{self._ipv6_group()}:{self._ipv6_group()}"
|
||
)
|
||
|
||
def _link_local(self, iface: str) -> str:
|
||
return f"fe80::{self._ipv6_group()}:{self._ipv6_group()}:fe{self._ipv6_group()[:2]}:{self._ipv6_group()}%{iface}"
|
||
|
||
def _ipv6_map(self) -> dict[str, str]:
|
||
ifaces = [
|
||
"rmnet_data1",
|
||
"",
|
||
"",
|
||
"wlan0",
|
||
"",
|
||
"",
|
||
"tun0",
|
||
"rmnet_data3",
|
||
"ifb0",
|
||
"",
|
||
"r_rmnet_data0",
|
||
"rmnet_data4",
|
||
"rmnet_data2",
|
||
"ifb1",
|
||
"dummy0",
|
||
"",
|
||
"ifb2",
|
||
"vgate0",
|
||
]
|
||
values: dict[str, str] = {"0": self._ipv6_addr()}
|
||
for index, iface in enumerate(ifaces, 1):
|
||
if iface:
|
||
values[str(index)] = self._link_local(iface)
|
||
else:
|
||
values[str(index)] = self._ipv6_addr(prefix=self._random.choice(["2408", "fd42", "2a00"]))
|
||
return values
|
||
|
||
|
||
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)
|