#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
2520 - Jetson端模糊测试模块集合
仅包含Jetson特有的模糊测试目标（PC端无法直接测试的硬件/接口）
每个模块独立生成目标程序、运行模糊测试、监控崩溃并清理资源

模块列表：
1. CAN Bus Fuzz      - 模拟汽车CAN ECU
2. Serial/UART Fuzz  - 串口回显处理
3. Camera/V4L2 Fuzz  - 摄像头帧处理
4. I2C Sensor Fuzz   - 温度传感器读取
5. Ollama API Fuzz   - Ollama服务API
6. Network Protocol   - 自定义TCP服务
"""

import os, sys, json, time, socket, struct, subprocess, threading, random
import tempfile, shutil, signal, logging
from datetime import datetime
from typing import Optional

# ==================== 常量定义 ====================
TARGET_DIR = "/tmp/2520fuzz_targets"
LOG_DIR = "/tmp/2520fuzz_logs"
SIGNAL_MAP = {
    -11: "SIGSEGV", -6: "SIGABRT", -7: "SIGBUS",
    -8: "SIGFPE", -4: "SIGILL", -9: "SIGKILL",
}
SHELL_EXIT_MAP = {
    139: "SIGSEGV", 134: "SIGABRT", 135: "SIGBUS",
    136: "SIGFPE", 132: "SIGILL",
}

# ==================== 工具函数 ====================

def _signal_name(return_code: int) -> str:
    """根据退出码解析信号名称"""
    if return_code < 0:
        return SIGNAL_MAP.get(return_code, f"SIG{-return_code}")
    if return_code in SHELL_EXIT_MAP:
        return SHELL_EXIT_MAP[return_code]
    if return_code > 128:
        sig_num = return_code - 128
        return SIGNAL_MAP.get(-sig_num, f"SIG{sig_num}")
    return f"EXIT_{return_code}"


def _is_crash(return_code: int) -> bool:
    """判断退出码是否表示崩溃"""
    if return_code is None:
        return True
    if return_code == 0:
        return False
    if return_code < 0:
        return True
    if return_code in SHELL_EXIT_MAP:
        return True
    if return_code > 128:
        return (return_code - 128) in {sig for sig in [6, 7, 8, 11, 4, 9]}
    if return_code == 1:
        return False
    return True


def _run_with_timeout(cmd: list, timeout: float = 5.0, stdin_data: Optional[bytes] = None) -> dict:
    """运行子进程并监控结果，返回详细的退出信息"""
    t0 = time.time()
    try:
        proc = subprocess.Popen(
            cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE
        )
        stdout, stderr = proc.communicate(input=stdin_data, timeout=timeout)
        elapsed = time.time() - t0
        rc = proc.returncode
        return {
            "return_code": rc,
            "signal": _signal_name(rc) if _is_crash(rc) else None,
            "is_crash": _is_crash(rc),
            "elapsed": round(elapsed, 3),
            "stdout": stdout,
            "stderr": stderr,
        }
    except subprocess.TimeoutExpired:
        proc.kill()
        proc.wait()
        elapsed = time.time() - t0
        return {
            "return_code": -9,
            "signal": "SIGKILL",
            "is_crash": True,
            "elapsed": round(elapsed, 3),
            "stdout": b"",
            "stderr": b"(timeout)",
        }
    except FileNotFoundError:
        return {
            "return_code": -1,
            "signal": None,
            "is_crash": False,
            "elapsed": 0,
            "stdout": b"",
            "stderr": b"target not found",
        }


def _ensure_target_dir():
    """确保目标脚本目录存在"""
    os.makedirs(TARGET_DIR, exist_ok=True)
    os.makedirs(LOG_DIR, exist_ok=True)


def _write_target_script(script_name: str, script_content: str) -> str:
    """将目标脚本写入文件并赋予执行权限"""
    _ensure_target_dir()
    path = os.path.join(TARGET_DIR, script_name)
    with open(path, "w", encoding="utf-8") as f:
        f.write(script_content)
    os.chmod(path, 0o755)
    return path


def _now() -> str:
    """返回当前时间戳字符串"""
    return datetime.now().isoformat()


class FuzzModuleBase:
    """所有模糊测试模块的基类"""

    name = "base_module"
    description = "Base module"

    def __init__(self):
        self.status = "idle"  # idle | running | completed
        self.total_run = 0
        self.crashes = []
        self.log = []
        self.fuzz_thread = None
        self.stop_flag = False
        self.lock = threading.Lock()
        self.target_script_path = None

    def _log(self, msg: str):
        """记录日志带时间戳"""
        entry = f"[{_now()}] {msg}"
        self.log.append(entry)
        print(f"[{self.name}] {msg}")

    def _add_crash(self, sample: str, signal_name: str, return_code: int, target: str):
        """记录一次崩溃事件"""
        crash = {
            "timestamp": _now(),
            "sample": sample,
            "signal": signal_name,
            "return_code": return_code,
            "target": target,
        }
        self.crashes.append(crash)
        self._log(f"*** 崩溃 *** 信号={signal_name}, 样本={sample}")

    def setup(self) -> dict:
        """安装依赖、创建目标程序、准备环境"""
        raise NotImplementedError

    def get_status(self) -> dict:
        """获取当前模块状态 - 返回前端期望的数据结构"""
        with self.lock:
            crash_rate = "0.00%"
            if self.total_run > 0:
                rate = len(self.crashes) / self.total_run * 100
                crash_rate = f"{rate:.2f}%"

            # 构建前端期望的 status 对象
            status_obj = {
                "running": self.status == "running",
                "completed": self.status == "completed",
                "idle": self.status == "idle",
                "tested": self.total_run,
                "crashes": len(self.crashes),
                "crash_rate": crash_rate,
                # 添加前端期望的各种字段名别名
                "frames": self.total_run,      # CAN模块
                "formats": self.total_run,     # Camera模块
                "bytes": self.total_run,       # Serial模块
                "addrs": self.total_run,       # I2C模块
                "requests": self.total_run,    # Ollama模块
                "pkts": self.total_run,        # Network模块
                "anomalies": len(self.crashes), # Ollama模块用anomalies
            }

            # 添加最近崩溃记录 (最多5条)
            recent = []
            for c in self.crashes[-5:]:
                recent.append({
                    "time": c.get("timestamp", ""),
                    "sample": c.get("sample", ""),
                    "signal": c.get("signal", ""),
                })
            status_obj["recent_crashes"] = recent

            # 添加日志 (最近20条)
            logs = []
            for entry in self.log[-20:]:
                # 解析日志条目 "[timestamp] message"
                if entry.startswith("[") and "]" in entry:
                    time_end = entry.index("]")
                    time_str = entry[1:time_end]
                    msg = entry[time_end + 1:].strip()
                    level = "info"
                    if "崩溃" in msg or "crash" in msg.lower():
                        level = "error"
                    elif "完成" in msg or "success" in msg.lower():
                        level = "success"
                    elif "警告" in msg or "warn" in msg.lower():
                        level = "warn"
                    logs.append({"time": time_str, "msg": msg, "level": level})
                else:
                    logs.append({"time": "", "msg": entry, "level": "info"})
            status_obj["logs"] = logs

            return {
                "module_name": self.name,
                "description": self.description,
                "status": status_obj,  # 返回对象而非字符串
                "total_run": self.total_run,
                "crashes_found": len(self.crashes),
                "crash_rate": crash_rate,
            }

    def start_fuzz(self, params: dict) -> dict:
        """启动模糊测试"""
        raise NotImplementedError

    def stop_fuzz(self) -> dict:
        """停止模糊测试"""
        with self.lock:
            self.stop_flag = True
            self.status = "completed"
        self._log("模糊测试已停止")
        return {"success": True, "message": "Fuzz stopped"}

    def get_results(self) -> dict:
        """获取模糊测试结果 - 返回前端期望的数据结构"""
        with self.lock:
            crash_rate = "0.00%"
            if self.total_run > 0:
                rate = len(self.crashes) / self.total_run * 100
                crash_rate = f"{rate:.2f}%"

            # 构建前端期望的 status 对象
            status_obj = {
                "running": self.status == "running",
                "completed": self.status == "completed",
                "idle": self.status == "idle",
                "tested": self.total_run,
                "crashes": len(self.crashes),
                "crash_rate": crash_rate,
                # 添加前端期望的各种字段名别名
                "frames": self.total_run,      # CAN模块
                "formats": self.total_run,     # Camera模块
                "bytes": self.total_run,       # Serial模块
                "addrs": self.total_run,       # I2C模块
                "requests": self.total_run,    # Ollama模块
                "pkts": self.total_run,        # Network模块
                "anomalies": len(self.crashes), # Ollama模块用anomalies
            }

            # 添加最近崩溃记录
            recent = []
            for c in self.crashes[-10:]:
                recent.append({
                    "time": c.get("timestamp", ""),
                    "sample": c.get("sample", ""),
                    "signal": c.get("signal", ""),
                })
            status_obj["recent_crashes"] = recent

            # 添加日志
            logs = []
            for entry in self.log[-50:]:
                if entry.startswith("[") and "]" in entry:
                    time_end = entry.index("]")
                    time_str = entry[1:time_end]
                    msg = entry[time_end + 1:].strip()
                    level = "info"
                    if "崩溃" in msg or "crash" in msg.lower():
                        level = "error"
                    elif "完成" in msg or "success" in msg.lower():
                        level = "success"
                    elif "警告" in msg or "warn" in msg.lower():
                        level = "warn"
                    logs.append({"time": time_str, "msg": msg, "level": level})
                else:
                    logs.append({"time": "", "msg": entry, "level": "info"})
            status_obj["logs"] = logs

            return {
                "module_name": self.name,
                "description": self.description,
                "status": status_obj,
                "total_run": self.total_run,
                "crashes_found": len(self.crashes),
                "crash_rate": crash_rate,
                "crashes": list(self.crashes),
                "log": list(self.log),
            }

    def cleanup(self) -> dict:
        """清理资源"""
        with self.lock:
            self.stop_flag = True
            self.status = "idle"
            if self.target_script_path and os.path.exists(self.target_script_path):
                try:
                    os.remove(self.target_script_path)
                except Exception:
                    pass
        self._log("资源已清理")
        return {"success": True, "message": "Cleanup done"}


# ===========================================================================
# 模块1: CAN Bus Fuzz - 模拟汽车CAN ECU
# ===========================================================================
class CanFuzzModule(FuzzModuleBase):
    """CAN总线模糊测试模块
    目标: 模拟汽车CAN ECU，监听虚拟CAN接口
    漏洞: 收到CAN ID > 0x700 或数据包含 b"CRASH" 时崩溃
    """

    name = "can_fuzz"
    description = "CAN Bus汽车ECU模糊测试 (模拟vcan0)"

    def __init__(self):
        super().__init__()
        self.can_interface = "vcan0"

    def setup(self) -> dict:
        """创建目标脚本和虚拟CAN接口"""
        self._log("正在设置CAN模糊测试环境...")

        # 写入CAN目标脚本
        script = r"""#!/usr/bin/env python3
# CAN Fuzz Target - 模拟汽车ECU
# 漏洞: CAN ID > 0x700 或 data==b"CRASH" 时崩溃
import socket, struct, sys, os, time, select

CAN_ID_CRASH_THRESHOLD = 0x700
CRASH_DATA = b"CRASH"

try:
    s = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
    s.bind(("vcan0",))
    s.settimeout(1.0)
except Exception as e:
    # 回退: 从文件读取CAN帧
    fifo_path = "/tmp/2520fuzz_can_fifo"
    while True:
        time.sleep(0.1)
        if os.path.exists(fifo_path):
            try:
                with open(fifo_path, "rb") as f:
                    data = f.read()
                    if len(data) >= 8:
                        can_id = struct.unpack("I", data[:4])[0] & 0x7FF
                        dlen = data[4] & 0x0F
                        payload = data[8:8+dlen] if dlen <= 8 else data[8:16]
                        if can_id > CAN_ID_CRASH_THRESHOLD:
                            os._exit(11)
                        if payload == CRASH_DATA:
                            os._exit(6)
            except (FileNotFoundError, struct.error):
                pass
        else:
            try:
                os.mkfifo(fifo_path)
            except FileExistsError:
                pass
    os._exit(0)

while True:
    try:
        ready = select.select([s], [], [], 1.0)
        if not ready[0]:
            continue
        frame = s.recv(16)
        if len(frame) < 8:
            continue
        can_id = struct.unpack("I", frame[:4])[0] & 0x7FF
        dlen = frame[4] & 0x0F
        payload = frame[8:8+dlen] if dlen <= 8 else frame[8:16]
        if can_id > CAN_ID_CRASH_THRESHOLD:
            os._exit(11)
        if payload == CRASH_DATA:
            os._exit(6)
    except socket.timeout:
        continue
    except Exception:
        continue
"""
        self.target_script_path = _write_target_script("can_target.py", script)
        self._log(f"目标脚本已创建: {self.target_script_path}")

        # 尝试创建虚拟CAN接口 (非必须)
        try:
            subprocess.run(
                ["sudo", "modprobe", "can"],
                capture_output=True, timeout=5
            )
            subprocess.run(
                ["sudo", "modprobe", "vcan"],
                capture_output=True, timeout=5
            )
            subprocess.run(
                ["sudo", "ip", "link", "add", "dev", "vcan0", "type", "vcan"],
                capture_output=True, timeout=5
            )
            subprocess.run(
                ["sudo", "ip", "link", "set", "up", "vcan0"],
                capture_output=True, timeout=5
            )
            self._log("虚拟CAN接口 vcan0 已创建")
        except Exception:
            self._log("无法创建vcan0 (可能缺少can/vcan内核模块)，将使用FIFO回退模式")

        return {"success": True, "message": "CAN模糊测试环境已就绪"}

    def _generate_can_samples(self, count: int = 100) -> list:
        """生成CAN模糊测试样本列表
        返回: [(can_id_hex, data_hex), ...]
        """
        samples = []
        for _ in range(count):
            # 随机CAN ID (0x000-0x7FF)，偶尔超出
            if random.random() < 0.1:
                can_id = random.randint(0x700, 0x7FF)  # 触发区域
            else:
                can_id = random.randint(0x000, 0x6FF)
            # 随机数据载荷 (0-8字节)
            data_len = random.randint(0, 8)
            data = bytes(random.randint(0x00, 0xFF) for _ in range(data_len))
            # 偶尔插入"CRASH"触发
            if random.random() < 0.05:
                data = b"CRASH"
                data_len = len(data)
            samples.append((can_id, data))
        return samples

    def start_fuzz(self, params: dict) -> dict:
        """启动CAN模糊测试"""
        if self.status == "running":
            return {"success": False, "error": "模糊测试已在运行中"}

        with self.lock:
            self.stop_flag = False
            self.total_run = 0
            self.crashes = []
            self.status = "running"

        count = params.get("count", 200)
        timeout = params.get("timeout", 3.0)
        self._log(f"启动CAN模糊测试: {count}次迭代, 超时={timeout}s")

        def _worker():
            samples = self._generate_can_samples(count)
            fifo_path = "/tmp/2520fuzz_can_fifo"
            target_cmd = ["python3", self.target_script_path]

            # 启动目标进程（后台）
            target_proc = subprocess.Popen(
                target_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
            )
            time.sleep(0.5)  # 等待目标启动

            for can_id, data in samples:
                if self.stop_flag:
                    break

                # 构造CAN帧 (前4字节ID, 1字节长度, 8字节数据)
                frame_id = can_id & 0x7FF
                frame = struct.pack("I", frame_id)
                frame += bytes([len(data)])
                frame += bytes([0, 0, 0])  # padding
                frame += data.ljust(8, b"\x00")[:8]

                # 通过FIFO发送
                try:
                    with open(fifo_path, "wb") as f:
                        f.write(frame)
                except Exception:
                    pass

                time.sleep(0.02)  # 给目标处理时间

                # 检查目标进程状态
                ret = target_proc.poll()
                if ret is not None:
                    # 目标已退出，检查是否崩溃
                    if _is_crash(ret) if ret is not None else False:
                        sig_name = _signal_name(ret) if ret is not None else "UNKNOWN"
                        sample_desc = f"can_id=0x{can_id:03X}, data={data.hex()}"
                        with self.lock:
                            self._add_crash(sample_desc, sig_name, ret or 0, "CAN ECU Simulator")
                    # 重启目标
                    target_proc = subprocess.Popen(
                        target_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                    )
                    time.sleep(0.3)

                with self.lock:
                    self.total_run += 1

            # 清理目标进程
            target_proc.terminate()
            try:
                target_proc.wait(timeout=2)
            except subprocess.TimeoutExpired:
                target_proc.kill()
            with self.lock:
                self.status = "completed"
            self._log(f"CAN模糊测试完成, 运行={self.total_run}, 崩溃={len(self.crashes)}")

        self.fuzz_thread = threading.Thread(target=_worker, daemon=True)
        self.fuzz_thread.start()
        return {"success": True, "message": f"CAN模糊测试已启动, {count}次迭代"}

    def cleanup(self) -> dict:
        """清理CAN环境"""
        super().cleanup()
        try:
            # 清理FIFO
            fifo = "/tmp/2520fuzz_can_fifo"
            if os.path.exists(fifo):
                os.remove(fifo)
            # 清理vcan0
            subprocess.run(
                ["sudo", "ip", "link", "delete", "vcan0"],
                capture_output=True, timeout=5
            )
        except Exception:
            pass
        return {"success": True, "message": "CAN环境已清理"}


# ===========================================================================
# 模块2: Serial/UART Fuzz - Jetson串口模糊测试
# ===========================================================================
class SerialFuzzModule(FuzzModuleBase):
    """串口(UART)模糊测试模块
    目标: 模拟串口数据处理服务
    漏洞: 收到包含 0xDE 0xAD 0xBE 0xEF 序列时崩溃
    """

    name = "serial_fuzz"
    description = "Serial/UART串口模糊测试 (使用PTY模拟)"

    def __init__(self):
        super().__init__()
        self.pty_master = None
        self.pty_slave = None
        self.pty_path = None

    def setup(self) -> dict:
        """创建目标脚本和伪终端"""
        self._log("正在设置串口模糊测试环境...")

        # 写入串口目标脚本
        script = r"""#!/usr/bin/env python3
# Serial Fuzz Target - 模拟串口数据处理
# 漏洞: 接收数据包含 0xDE 0xAD 0xBE 0xEF 时崩溃
import sys, os, time, select

CRASH_SEQ = b"\xDE\xAD\xBE\xEF"
CRASH_LONG_LEN = 10000

if len(sys.argv) > 1:
    port_path = sys.argv[1]
else:
    port_path = "/tmp/2520fuzz_serial_pty"

# 从FIFO或PTY读取
while True:
    try:
        if os.path.exists(port_path):
            with open(port_path, "rb") as f:
                while True:
                    data = f.read(1024)
                    if not data:
                        break
                    if len(data) >= CRASH_LONG_LEN:
                        os._exit(11)
                    if CRASH_SEQ in data:
                        os._exit(6)
                    if any(b in data for b in [b"\x00" * 20, b"\xFF" * 20]):
                        os._exit(7)
            time.sleep(0.1)
        else:
            time.sleep(0.5)
            try:
                os.mkfifo(port_path)
            except FileExistsError:
                pass
    except Exception:
        time.sleep(0.5)
"""
        self.target_script_path = _write_target_script("serial_target.py", script)
        self._log(f"目标脚本已创建: {self.target_script_path}")

        # 创建PTY路径（FIFO）
        pty_path = "/tmp/2520fuzz_serial_pty"
        if not os.path.exists(pty_path):
            try:
                os.mkfifo(pty_path)
            except FileExistsError:
                pass
        self.pty_path = pty_path
        self._log(f"串口FIFO已创建: {pty_path}")

        return {"success": True, "message": "串口模糊测试环境已就绪"}

    def _generate_serial_samples(self, count: int = 100) -> list:
        """生成串口模糊测试样本"""
        samples = []
        for _ in range(count):
            choice = random.random()
            if choice < 0.05:
                # 触发序列
                samples.append(b"\xDE\xAD\xBE\xEF")
            elif choice < 0.1:
                # 超长数据
                samples.append(bytes(random.randint(0x00, 0xFF) for _ in range(15000)))
            elif choice < 0.15:
                # 控制字符注入
                samples.append(b"\x00" * random.randint(10, 30))
            elif choice < 0.2:
                samples.append(b"\xFF" * random.randint(10, 30))
            else:
                # 随机序列
                length = random.randint(0, 128)
                samples.append(bytes(random.randint(0x00, 0xFF) for _ in range(length)))
        return samples

    def start_fuzz(self, params: dict) -> dict:
        """启动串口模糊测试"""
        if self.status == "running":
            return {"success": False, "error": "模糊测试已在运行中"}

        with self.lock:
            self.stop_flag = False
            self.total_run = 0
            self.crashes = []
            self.status = "running"

        count = params.get("count", 150)
        timeout_val = params.get("timeout", 3.0)
        self._log(f"启动串口模糊测试: {count}次迭代")

        def _worker():
            samples = self._generate_serial_samples(count)
            target_cmd = ["python3", self.target_script_path, self.pty_path]

            # 启动目标
            target_proc = subprocess.Popen(
                target_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
            )
            time.sleep(0.5)

            for data in samples:
                if self.stop_flag:
                    break

                # 写入数据到FIFO
                try:
                    with open(self.pty_path, "wb") as f:
                        f.write(data)
                except Exception:
                    pass

                time.sleep(0.05)

                # 检查目标状态
                ret = target_proc.poll()
                if ret is not None:
                    if ret != 0 and _is_crash(ret):
                        sig_name = _signal_name(ret)
                        sample_desc = f"data_len={len(data)}, hex={data[:20].hex()}"
                        with self.lock:
                            self._add_crash(sample_desc, sig_name, ret, "Serial Processor")
                    # 重启目标
                    target_proc = subprocess.Popen(
                        target_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                    )
                    time.sleep(0.3)

                with self.lock:
                    self.total_run += 1

            target_proc.terminate()
            try:
                target_proc.wait(timeout=2)
            except subprocess.TimeoutExpired:
                target_proc.kill()
            with self.lock:
                self.status = "completed"
            self._log(f"串口模糊测试完成, 运行={self.total_run}, 崩溃={len(self.crashes)}")

        self.fuzz_thread = threading.Thread(target=_worker, daemon=True)
        self.fuzz_thread.start()
        return {"success": True, "message": f"串口模糊测试已启动, {count}次迭代"}

    def cleanup(self) -> dict:
        """清理串口资源"""
        super().cleanup()
        if self.pty_path and os.path.exists(self.pty_path):
            try:
                os.remove(self.pty_path)
            except Exception:
                pass
        return {"success": True, "message": "串口环境已清理"}


# ===========================================================================
# 模块3: Camera/V4L2 Fuzz - Jetson摄像头模糊测试
# ===========================================================================
class CameraFuzzModule(FuzzModuleBase):
    """摄像头(V4L2)模糊测试模块
    目标: 模拟摄像头帧处理器 (IMX219)
    漏洞: 超大分辨率或特定像素模式导致崩溃
    """

    name = "camera_fuzz"
    description = "Camera/V4L2摄像头帧处理模糊测试 (模拟模式)"

    def __init__(self):
        super().__init__()
        self.video_dev = "/dev/video0"
        self.test_mode = True  # 默认使用合成帧测试

    def setup(self) -> dict:
        """创建目标脚本"""
        self._log("正在设置摄像头模糊测试环境...")

        # 检查实际摄像头设备
        if os.path.exists(self.video_dev):
            self.test_mode = False
            self._log(f"检测到摄像头设备: {self.video_dev}")
        else:
            self.test_mode = True
            self._log(f"{self.video_dev} 不存在, 使用合成帧测试模式")

        script = r"""#!/usr/bin/env python3
# Camera Fuzz Target - 模拟摄像头帧处理器
# 漏洞: 超大分辨率或特定像素模式时崩溃
import sys, os, struct, json

CRASH_PIXEL_PATTERN = b"\xFF\x00\xFF\x00" * 500
CRASH_MIN_DIM = 10000

def process_frame(width, height, pixel_data):
    # 处理一帧图像数据
    if width > CRASH_MIN_DIM or height > CRASH_MIN_DIM:
        os._exit(11)  # SIGSEGV - 模拟缓冲区溢出
    total_pixels = width * height
    if total_pixels > 50000000:
        os._exit(11)
    if len(pixel_data) >= 4:
        if pixel_data[:4] == b"\xDE\xAD\xBE\xEF":
            os._exit(6)  # SIGABRT
    if CRASH_PIXEL_PATTERN in pixel_data:
        os._exit(7)  # SIGBUS
    # 检查无效的FourCC
    if len(pixel_data) >= 4:
        fourcc = pixel_data[:4]
        invalid_fourcc = [b"ZZZZ", b"!!!!", b"XXXX", b"####"]
        if fourcc in invalid_fourcc:
            os._exit(4)  # SIGILL

if len(sys.argv) > 1:
    frame_file = sys.argv[1]
    if os.path.exists(frame_file):
        with open(frame_file, "rb") as f:
            raw = f.read()
        if len(raw) >= 8:
            w = struct.unpack("I", raw[:4])[0]
            h = struct.unpack("I", raw[4:8])[0]
            process_frame(w, h, raw[8:])
    os._exit(0)

# 从FIFO读取帧
fifo = "/tmp/2520fuzz_camera_fifo"
if os.path.exists(fifo):
    with open(fifo, "rb") as f:
        raw = f.read()
        if len(raw) >= 8:
            w = struct.unpack("I", raw[:4])[0]
            h = struct.unpack("I", raw[4:8])[0]
            process_frame(w, h, raw[8:])
"""
        self.target_script_path = _write_target_script("camera_target.py", script)
        self._log(f"目标脚本已创建: {self.target_script_path}")

        # 创建FIFO
        fifo = "/tmp/2520fuzz_camera_fifo"
        if not os.path.exists(fifo):
            try:
                os.mkfifo(fifo)
            except FileExistsError:
                pass

        return {"success": True, "message": "摄像头模糊测试环境已就绪"}

    def _generate_frame_samples(self, count: int = 100) -> list:
        """生成摄像头帧样本
        每个样本: (width, height, pixel_data)
        """
        samples = []
        for _ in range(count):
            choice = random.random()
            if choice < 0.05:
                # 超大分辨率 (触发缓冲区溢出)
                w = random.randint(10001, 20000)
                h = random.randint(10001, 20000)
                pixel_data = b"\x00" * 64
            elif choice < 0.1:
                # 触发像素模式崩溃
                w, h = 640, 480
                pixel_data = b"\xFF\x00\xFF\x00" * 600
            elif choice < 0.15:
                # 特殊标记
                w, h = 1920, 1080
                pixel_data = b"\xDE\xAD\xBE\xEF" + os.urandom(60)
            elif choice < 0.2:
                # 无效FourCC
                w, h = 1280, 720
                pixel_data = b"ZZZZ" + os.urandom(60)
            elif choice < 0.25:
                # 极端尺寸
                w = random.randint(0, 10)
                h = random.randint(0, 10)
                pixel_data = os.urandom(random.randint(0, 128))
            else:
                # 正常帧 + 随机噪声
                w = random.randint(16, 4096)
                h = random.randint(16, 4096)
                pixel_data = os.urandom(random.randint(0, 256))
            samples.append((w, h, pixel_data))
        return samples

    def start_fuzz(self, params: dict) -> dict:
        """启动摄像头模糊测试"""
        if self.status == "running":
            return {"success": False, "error": "模糊测试已在运行中"}

        with self.lock:
            self.stop_flag = False
            self.total_run = 0
            self.crashes = []
            self.status = "running"

        count = params.get("count", 150)
        self._log(f"启动摄像头模糊测试: {count}次迭代 (模式={'模拟' if self.test_mode else '真实'})")

        def _worker():
            samples = self._generate_frame_samples(count)
            target_cmd = ["python3", self.target_script_path]
            fifo = "/tmp/2520fuzz_camera_fifo"

            target_proc = subprocess.Popen(
                target_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
            )
            time.sleep(0.3)

            for w, h, pixel_data in samples:
                if self.stop_flag:
                    break

                # 构造帧: [width:4][height:4][pixel_data]
                frame = struct.pack("II", w, h) + pixel_data

                try:
                    with open(fifo, "wb") as f:
                        f.write(frame)
                except Exception:
                    pass

                time.sleep(0.05)

                ret = target_proc.poll()
                if ret is not None:
                    if ret != 0 and _is_crash(ret):
                        sig_name = _signal_name(ret)
                        desc = f"width={w}, height={h}, pixel_len={len(pixel_data)}"
                        with self.lock:
                            self._add_crash(desc, sig_name, ret, "Camera Frame Processor")
                    target_proc = subprocess.Popen(
                        target_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                    )
                    time.sleep(0.3)

                with self.lock:
                    self.total_run += 1

            target_proc.terminate()
            try:
                target_proc.wait(timeout=2)
            except subprocess.TimeoutExpired:
                target_proc.kill()
            with self.lock:
                self.status = "completed"
            self._log(f"摄像头模糊测试完成, 运行={self.total_run}, 崩溃={len(self.crashes)}")

        self.fuzz_thread = threading.Thread(target=_worker, daemon=True)
        self.fuzz_thread.start()
        return {"success": True, "message": f"摄像头模糊测试已启动, {count}次迭代"}

    def cleanup(self) -> dict:
        """清理摄像头资源"""
        super().cleanup()
        fifo = "/tmp/2520fuzz_camera_fifo"
        if os.path.exists(fifo):
            try:
                os.remove(fifo)
            except Exception:
                pass
        return {"success": True, "message": "摄像头环境已清理"}


# ===========================================================================
# 模块4: I2C Sensor Fuzz - Jetson I2C温度传感器模糊测试
# ===========================================================================
class I2CSensorFuzzModule(FuzzModuleBase):
    """I2C传感器模糊测试模块
    目标: 温度传感器数据解析器
    漏洞: 异常温度值或格式损坏导致崩溃
    """

    name = "i2c_sensor_fuzz"
    description = "I2C温度传感器数据解析模糊测试"

    def __init__(self):
        super().__init__()
        self.thermal_zones = []
        self.sim_device = None

    def setup(self) -> dict:
        """创建目标脚本并扫描温度传感器"""
        self._log("正在设置I2C传感器模糊测试环境...")

        script = r"""#!/usr/bin/env python3
# I2C Sensor Fuzz Target - 温度传感器数据解析器
# 漏洞: 异常温度值或格式损坏导致崩溃
import sys, os

MAX_TEMP = 150000  # 150°C
MIN_TEMP = -55000  # -55°C
CRASH_TEMP = 999999

def parse_temperature(raw_str):
    # 解析温度字符串 (单位: 毫摄氏度)
    raw = raw_str.strip()
    if not raw:
        os._exit(6)
    if len(raw) > 20:
        os._exit(11)
    temp = int(raw)
    if temp == CRASH_TEMP:
        os._exit(11)
    if abs(temp) > 1000000:
        os._exit(7)
    if temp > MAX_TEMP or temp < MIN_TEMP:
        os._exit(8)
    return temp / 1000.0

if len(sys.argv) > 1:
    result = parse_temperature(sys.argv[1])
    print(result)
    os._exit(0)

# FIFO模式
fifo = "/tmp/2520fuzz_i2c_fifo"
if os.path.exists(fifo):
    with open(fifo, "r") as f:
        for line in f:
            line = line.strip()
            if line:
                parse_temperature(line)
"""
        self.target_script_path = _write_target_script("i2c_sensor_target.py", script)
        self._log(f"目标脚本已创建: {self.target_script_path}")

        # 扫描实际温度传感器
        import glob as _glob
        zones = _glob.glob("/sys/class/thermal/thermal_zone*/temp")
        if zones:
            self.thermal_zones = zones
            self._log(f"检测到 {len(zones)} 个温度传感器")
            for z in zones:
                try:
                    with open(z, "r") as f:
                        val = f.read().strip()
                    self._log(f"  {z}: {val}")
                except Exception:
                    pass
        else:
            self._log("未检测到温度传感器设备, 使用纯模拟模式")

        # 创建FIFO
        fifo = "/tmp/2520fuzz_i2c_fifo"
        if not os.path.exists(fifo):
            try:
                os.mkfifo(fifo)
            except FileExistsError:
                pass

        return {"success": True, "message": "I2C传感器模糊测试环境已就绪"}

    def _generate_sensor_samples(self, count: int = 100) -> list:
        """生成传感器数据样本 (原始温度字符串)"""
        samples = []
        for _ in range(count):
            choice = random.random()
            if choice < 0.05:
                # 崩溃值
                samples.append("999999")
            elif choice < 0.1:
                # 超大值
                samples.append(str(random.randint(200000, 99999999)))
            elif choice < 0.15:
                # 超小值
                samples.append(str(random.randint(-99999999, -100000)))
            elif choice < 0.2:
                # 空字符串
                samples.append("")
            elif choice < 0.25:
                # 超长字符串
                samples.append("1" * random.randint(30, 100))
            elif choice < 0.3:
                # 无效格式 (非数字)
                chars = "abcxyz!@#$%^&*()_+-=[]{}|;':\",./<>?~"
                samples.append("".join(random.choice(chars) for _ in range(random.randint(1, 20))))
            elif choice < 0.35:
                # 读无效I2C地址 (模拟)
                samples.append(f"i2c_addr=0x{random.randint(0x50, 0xFF):02x}_val={random.randint(-9999, 99999)}")
            else:
                # 正常范围值
                samples.append(str(random.randint(-40000, 120000)))
        return samples

    def start_fuzz(self, params: dict) -> dict:
        """启动I2C传感器模糊测试"""
        if self.status == "running":
            return {"success": False, "error": "模糊测试已在运行中"}

        with self.lock:
            self.stop_flag = False
            self.total_run = 0
            self.crashes = []
            self.status = "running"

        count = params.get("count", 200)
        self._log(f"启动I2C传感器模糊测试: {count}次迭代")

        def _worker():
            samples = self._generate_sensor_samples(count)
            fifo = "/tmp/2520fuzz_i2c_fifo"
            target_cmd = ["python3", self.target_script_path]

            # 启动目标 (FIFO模式)
            target_proc = subprocess.Popen(
                target_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
            )
            time.sleep(0.3)

            for sample in samples:
                if self.stop_flag:
                    break

                # 通过FIFO发送样本
                try:
                    with open(fifo, "w") as f:
                        f.write(sample + "\n")
                except Exception:
                    pass

                time.sleep(0.03)

                # 同时也尝试直接命令行模式
                cli_proc = subprocess.Popen(
                    target_cmd + [sample],
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE
                )
                try:
                    cli_ret = cli_proc.wait(timeout=2)
                    if cli_ret != 0 and _is_crash(cli_ret):
                        sig_name = _signal_name(cli_ret)
                        with self.lock:
                            self._add_crash(
                                f"temp_str={sample[:50]}", sig_name, cli_ret,
                                "Temperature Sensor Parser"
                            )
                except subprocess.TimeoutExpired:
                    cli_proc.kill()

                # 检查FIFO目标
                ret = target_proc.poll()
                if ret is not None:
                    if ret != 0 and _is_crash(ret):
                        sig_name = _signal_name(ret)
                        with self.lock:
                            self._add_crash(
                                f"temp_str={sample[:50]}", sig_name, ret,
                                "Temperature Sensor Parser (FIFO)"
                            )
                    target_proc = subprocess.Popen(
                        target_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                    )
                    time.sleep(0.3)

                with self.lock:
                    self.total_run += 1

            target_proc.terminate()
            try:
                target_proc.wait(timeout=2)
            except subprocess.TimeoutExpired:
                target_proc.kill()
            with self.lock:
                self.status = "completed"
            self._log(f"I2C传感器模糊测试完成, 运行={self.total_run}, 崩溃={len(self.crashes)}")

        self.fuzz_thread = threading.Thread(target=_worker, daemon=True)
        self.fuzz_thread.start()
        return {"success": True, "message": f"I2C传感器模糊测试已启动, {count}次迭代"}

    def cleanup(self) -> dict:
        """清理I2C资源"""
        super().cleanup()
        fifo = "/tmp/2520fuzz_i2c_fifo"
        if os.path.exists(fifo):
            try:
                os.remove(fifo)
            except Exception:
                pass
        return {"success": True, "message": "I2C传感器环境已清理"}


# ===========================================================================
# 模块5: Ollama API Fuzz - Jetson Ollama服务模糊测试
# ===========================================================================
class OllamaFuzzModule(FuzzModuleBase):
    """Ollama API模糊测试模块
    目标: 本地Ollama服务 (http://localhost:11434)
    不创建目标程序，直接攻击已有API
    """

    name = "ollama_fuzz"
    description = "Ollama API服务模糊测试 (localhost:11434)"

    def __init__(self):
        super().__init__()
        self.ollama_host = "http://localhost:11434"
        self.api_available = False

    def setup(self) -> dict:
        """检测Ollama服务状态"""
        self._log("正在检测Ollama服务...")

        try:
            resp = subprocess.run(
                ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
                 f"{self.ollama_host}/api/tags"],
                capture_output=True, timeout=10, text=True
            )
            if resp.stdout.strip() == "200":
                self.api_available = True
                self._log("Ollama API服务运行正常")
            else:
                self._log(f"Ollama服务响应异常: HTTP {resp.stdout.strip()}")
        except Exception as e:
            self._log(f"无法连接Ollama服务: {e}")
            self._log("将使用进程存在性检查模式")

        # 检查ollama进程
        try:
            proc_check = subprocess.run(
                ["pgrep", "-x", "ollama"],
                capture_output=True, timeout=5
            )
            if proc_check.returncode == 0:
                self._log("Ollama进程存在")
            else:
                self._log("未找到Ollama进程(nit may not be running)")
        except Exception:
            pass

        return {
            "success": True,
            "api_available": self.api_available,
            "message": "Ollama模糊测试环境已就绪",
        }

    def _generate_api_samples(self, count: int = 100) -> list:
        """生成Ollama API请求样本
        返回: [(endpoint, json_payload, description), ...]
        """
        endpoints = ["/api/generate", "/api/chat", "/api/embeddings", "/api/tags", "/api/show"]
        samples = []

        for _ in range(count):
            ep = random.choice(endpoints)
            choice = random.random()

            if choice < 0.1:
                # 无效JSON (格式破坏)
                payload = '{"model": "llama2", "prompt": "' + "A" * random.randint(10, 100) + '"'
                desc = "malformed_json_missing_brace"
            elif choice < 0.15:
                # 路径遍历
                payload = '{"model": "../../../etc/passwd", "prompt": "hello"}'
                desc = "path_traversal_model"
            elif choice < 0.2:
                # 超长prompt
                long_str = "A" * random.randint(50000, 200000)
                payload = json.dumps({"model": "llama2", "prompt": long_str})
                desc = "long_prompt_overflow"
            elif choice < 0.25:
                # 特殊字符注入
                inj = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x1f\x7f\\\"'`$();|><&"
                payload = json.dumps({"model": "llama2", "prompt": inj})
                desc = "special_char_injection"
            elif choice < 0.3:
                # 无效字段类型
                payload = json.dumps({"model": ["llama2", "mistral"], "prompt": 12345})
                desc = "invalid_field_types"
            elif choice < 0.35:
                # 超大嵌套JSON
                deep = {"a": {"b": {"c": {"d": {"e": "f" * 1000}}}}}
                payload = json.dumps(deep)
                desc = "deep_nested_json"
            elif choice < 0.4:
                # 空字段
                payload = json.dumps({"model": "", "prompt": ""})
                desc = "empty_fields"
            else:
                # 随机字段组合
                rand_fields = {
                    "model": random.choice(["", "llama2", "../..", " ", None, "a" * 1000]),
                    "prompt": "test" + "x" * random.randint(0, 500),
                    "options": {"temperature": random.uniform(-10, 100)},
                    "stream": random.choice([True, False, "true", 1, None]),
                }
                payload = json.dumps(rand_fields, default=str)
                desc = "random_field_combination"

            samples.append((ep, payload, desc))
        return samples

    def start_fuzz(self, params: dict) -> dict:
        """启动Ollama API模糊测试"""
        if self.status == "running":
            return {"success": False, "error": "模糊测试已在运行中"}

        with self.lock:
            self.stop_flag = False
            self.total_run = 0
            self.crashes = []
            self.status = "running"

        count = params.get("count", 100)
        self._log(f"启动Ollama API模糊测试: {count}次迭代")

        def _worker():
            samples = self._generate_api_samples(count)

            for endpoint, payload, desc in samples:
                if self.stop_flag:
                    break

                try:
                    curl_cmd = [
                        "curl", "-s", "-o", "/dev/null", "-w",
                        "%{http_code}:%{time_total}:%{size_download}",
                        "-X", "POST",
                        f"{self.ollama_host}{endpoint}",
                        "-d", payload,
                        "--max-time", "10",
                    ]
                    result = subprocess.run(
                        curl_cmd, capture_output=True, timeout=15, text=True
                    )
                    output = result.stdout.strip()
                    parts = output.split(":")
                    http_code = parts[0] if parts else "000"

                    # 检查ollama进程是否还在运行
                    proc_check = subprocess.run(
                        ["pgrep", "-x", "ollama"],
                        capture_output=True, timeout=5
                    )
                    ollama_alive = proc_check.returncode == 0

                    crash_detected = False
                    crash_signal = None

                    if not ollama_alive:
                        crash_detected = True
                        crash_signal = "SIGKILL"
                    elif http_code in ("500", "502", "503"):
                        crash_detected = True
                        crash_signal = f"HTTP_{http_code}"

                    if crash_detected:
                        with self.lock:
                            self._add_crash(
                                f"{desc} | {endpoint} | HTTP={http_code} | payload_len={len(payload)}",
                                crash_signal, -1, "Ollama API Service"
                            )
                except subprocess.TimeoutExpired:
                    with self.lock:
                        self._add_crash(
                            f"{desc} | {endpoint} | timeout",
                            "TIMEOUT", -9, "Ollama API Service"
                        )
                except Exception as e:
                    self._log(f"请求异常: {e}")

                with self.lock:
                    self.total_run += 1

            with self.lock:
                self.status = "completed"
            self._log(f"Ollama API模糊测试完成, 运行={self.total_run}, 崩溃={len(self.crashes)}")

        self.fuzz_thread = threading.Thread(target=_worker, daemon=True)
        self.fuzz_thread.start()
        return {"success": True, "message": f"Ollama API模糊测试已启动, {count}次迭代"}

    def cleanup(self) -> dict:
        """清理（无操作）"""
        super().cleanup()
        return {"success": True, "message": "Ollama环境无需清理"}


# ===========================================================================
# 模块6: Network Protocol Fuzz - Jetson自定义TCP服务模糊测试
# ===========================================================================
class NetworkFuzzModule(FuzzModuleBase):
    """网络协议模糊测试模块
    目标: 自定义TCP服务 (port 17888)
    漏洞: 畸形JSON或超长字符串导致崩溃
    """

    name = "network_fuzz"
    description = "自定义TCP协议模糊测试 (port 17888)"

    def __init__(self):
        super().__init__()
        self.server_port = 17888
        self.server_proc = None

    def setup(self) -> dict:
        """创建TCP目标服务和客户端测试工具"""
        self._log("正在设置网络协议模糊测试环境...")

        # 写入TCP服务端目标脚本
        server_script = r"""#!/usr/bin/env python3
# Network Fuzz Target Server - 自定义TCP协议服务
# 漏洞: 畸形JSON或超长字符串导致崩溃
import socket, json, sys, os, threading, time

PORT = 17888
MAX_MSG_LEN = 4096
CRASH_LONG_STR_LEN = 10000
CRASH_PROTOCOL_VERSION = "9.9.9"

def handle_client(conn, addr):
    try:
        data = conn.recv(65536)
        if not data:
            conn.close()
            return
        # 长度检查
        if len(data) > 65536:
            os._exit(11)
        msg = data.decode("utf-8", errors="replace")
        if len(msg) > CRASH_LONG_STR_LEN:
            os._exit(11)
        # 解析JSON
        try:
            payload = json.loads(msg)
        except json.JSONDecodeError:
            conn.send(b'{"error":"invalid_json"}\n')
            conn.close()
            return
        # 协议版本检查
        version = payload.get("version", "1.0")
        if version == CRASH_PROTOCOL_VERSION:
            os._exit(6)
        # 消息类型处理
        msg_type = payload.get("type", "")
        data_field = payload.get("data", "")
        if isinstance(data_field, str) and len(data_field) > 5000:
            os._exit(7)
        conn.send(b'{"status":"ok"}\n')
        conn.close()
    except Exception:
        conn.close()

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(("0.0.0.0", PORT))
    s.listen(10)
    s.settimeout(1.0)
    while True:
        try:
            conn, addr = s.accept()
            t = threading.Thread(target=handle_client, args=(conn, addr), daemon=True)
            t.start()
        except socket.timeout:
            continue
        except Exception:
            break
    s.close()

if __name__ == "__main__":
    main()
"""
        self.target_script_path = _write_target_script("network_target_server.py", server_script)
        self._log(f"服务端脚本已创建: {self.target_script_path}")

        return {"success": True, "message": "网络协议模糊测试环境已就绪"}

    def _generate_network_samples(self, count: int = 100) -> list:
        """生成TCP协议测试样本
        返回: [(payload_bytes, description), ...]
        """
        samples = []
        for _ in range(count):
            choice = random.random()
            if choice < 0.05:
                # 畸形JSON (截断)
                payload = '{"version": "1.0", "type": "ping", "data": "' + "A" * random.randint(10, 100)
                desc = "malformed_json_truncated"
            elif choice < 0.1:
                # 超长字符串 (缓冲区溢出)
                data_str = "A" * random.randint(10000, 20000)
                payload = json.dumps({"version": "1.0", "type": "echo", "data": data_str})
                desc = "buffer_overflow_long_string"
            elif choice < 0.15:
                # 崩溃版本号
                payload = json.dumps({"version": "9.9.9", "type": "ping", "data": ""})
                desc = "crash_protocol_version"
            elif choice < 0.2:
                # 无效协议版本格式
                payload = json.dumps({"version": [1, 2, 3], "type": "ping"})
                desc = "invalid_version_format"
            elif choice < 0.25:
                # 超大JSON嵌套
                deep = {"version": "1.0", "type": "nested", "data": {"level1": {"level2": {"level3": "x" * 5000}}}}
                payload = json.dumps(deep)
                desc = "deep_nested_large_json"
            elif choice < 0.3:
                # 二进制垃圾数据
                raw_bytes = bytes(random.randint(0x00, 0xFF) for _ in range(random.randint(1, 5000)))
                payload = raw_bytes.decode("utf-8", errors="replace")
                desc = "binary_garbage"
            elif choice < 0.35:
                # 空消息
                payload = ""
                desc = "empty_message"
            elif choice < 0.4:
                # 超长消息
                payload = "A" * random.randint(100000, 200000)
                desc = "extremely_long_message"
            else:
                # 正常 + 随机噪声
                norm = json.dumps({
                    "version": f"{random.randint(1, 5)}.{random.randint(0, 9)}",
                    "type": random.choice(["ping", "echo", "query", "data"]),
                    "data": "test" + "x" * random.randint(0, 100),
                })
                payload = norm + "!" * random.randint(0, 10)
                desc = "normal_with_noise"

            samples.append((payload.encode("utf-8"), desc))
        return samples

    def start_fuzz(self, params: dict) -> dict:
        """启动网络协议模糊测试"""
        if self.status == "running":
            return {"success": False, "error": "模糊测试已在运行中"}

        with self.lock:
            self.stop_flag = False
            self.total_run = 0
            self.crashes = []
            self.status = "running"

        count = params.get("count", 150)
        self._log(f"启动网络协议模糊测试: {count}次迭代")

        def _worker():
            samples = self._generate_network_samples(count)
            server_cmd = ["python3", self.target_script_path]

            # 启动TCP服务端
            self.server_proc = subprocess.Popen(
                server_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
            )
            time.sleep(1.0)

            # 检查服务是否启动
            if self.server_proc.poll() is not None:
                self._log("服务端启动失败!")
                with self.lock:
                    self.status = "completed"
                return

            for payload_bytes, desc in samples:
                if self.stop_flag:
                    break

                # 检查服务端是否存活
                if self.server_proc.poll() is not None:
                    ret = self.server_proc.returncode
                    if _is_crash(ret):
                        sig_name = _signal_name(ret)
                        with self.lock:
                            self._add_crash(
                                f"{desc} | payload_len={len(payload_bytes)}",
                                sig_name, ret, "TCP Service (port 17888)"
                            )
                    # 重启服务端
                    self.server_proc = subprocess.Popen(
                        server_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                    )
                    time.sleep(0.5)

                # 发送TCP请求
                try:
                    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    sock.settimeout(5.0)
                    sock.connect(("127.0.0.1", self.server_port))
                    sock.sendall(payload_bytes)
                    try:
                        response = sock.recv(1024)
                    except socket.timeout:
                        response = b""
                    sock.close()

                    # 再次检查服务端状态
                    if self.server_proc.poll() is not None:
                        ret = self.server_proc.returncode
                        if _is_crash(ret):
                            sig_name = _signal_name(ret)
                            with self.lock:
                                self._add_crash(
                                    f"{desc} | payload_len={len(payload_bytes)}",
                                    sig_name, ret, "TCP Service (port 17888)"
                                )
                        # 重启
                        self.server_proc = subprocess.Popen(
                            server_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                        )
                        time.sleep(0.5)

                except (ConnectionRefusedError, ConnectionResetError, BrokenPipeError) as e:
                    # 连接失败可能意味着服务端已崩溃
                    if self.server_proc.poll() is not None:
                        ret = self.server_proc.returncode
                        if _is_crash(ret):
                            sig_name = _signal_name(ret)
                            with self.lock:
                                self._add_crash(
                                    f"{desc} | connection_error={str(e)[:50]}",
                                    sig_name, ret, "TCP Service (port 17888)"
                                )
                    # 重启
                    self.server_proc = subprocess.Popen(
                        server_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                    )
                    time.sleep(0.5)
                except socket.timeout:
                    pass
                except Exception as e:
                    self._log(f"TCP请求异常: {e}")

                with self.lock:
                    self.total_run += 1

            # 清理服务端
            if self.server_proc and self.server_proc.poll() is None:
                self.server_proc.terminate()
                try:
                    self.server_proc.wait(timeout=2)
                except subprocess.TimeoutExpired:
                    self.server_proc.kill()

            with self.lock:
                self.status = "completed"
            self._log(f"网络协议模糊测试完成, 运行={self.total_run}, 崩溃={len(self.crashes)}")

        self.fuzz_thread = threading.Thread(target=_worker, daemon=True)
        self.fuzz_thread.start()
        return {"success": True, "message": f"网络协议模糊测试已启动, {count}次迭代"}

    def cleanup(self) -> dict:
        """清理网络资源"""
        super().cleanup()
        if self.server_proc and self.server_proc.poll() is None:
            try:
                self.server_proc.terminate()
                self.server_proc.wait(timeout=2)
            except Exception:
                try:
                    self.server_proc.kill()
                except Exception:
                    pass
        return {"success": True, "message": "网络环境已清理"}


# ===========================================================================
# 模块注册表 - jetson_agent.py 通过此字典加载所有模块
# ===========================================================================
MODULES = {
    "can_fuzz": CanFuzzModule(),
    "serial_fuzz": SerialFuzzModule(),
    "camera_fuzz": CameraFuzzModule(),
    "i2c_sensor_fuzz": I2CSensorFuzzModule(),
    "ollama_fuzz": OllamaFuzzModule(),
    "network_fuzz": NetworkFuzzModule(),
}

# 模块描述列表 (用于快速查看)
MODULE_LIST = [
    {
        "name": mod.name,
        "description": mod.description,
        "class": type(mod).__name__,
    }
    for mod in MODULES.values()
]


# ==================== 独立测试 ====================
if __name__ == "__main__":
    print("=" * 60)
    print("2520 Jetson模糊测试模块 - 独立测试")
    print("=" * 60)

    if len(sys.argv) > 1 and sys.argv[1] == "list":
        print(f"\n已注册 {len(MODULE_LIST)} 个模块:\n")
        for m in MODULE_LIST:
            print(f"  [{m['name']}] {m['description']}")
        sys.exit(0)

    if len(sys.argv) > 2:
        module_name = sys.argv[1]
        action = sys.argv[2]

        if module_name not in MODULES:
            print(f"错误: 未知模块 '{module_name}'")
            print(f"可用模块: {list(MODULES.keys())}")
            sys.exit(1)

        mod = MODULES[module_name]

        if action == "setup":
            result = mod.setup()
            print(json.dumps(result, ensure_ascii=False, indent=2))

        elif action == "status":
            result = mod.get_status()
            print(json.dumps(result, ensure_ascii=False, indent=2))

        elif action == "fuzz":
            count = int(sys.argv[3]) if len(sys.argv) > 3 else 50
            result = mod.start_fuzz({"count": count})
            print(json.dumps(result, ensure_ascii=False, indent=2))
            # 等待完成
            while mod.status == "running":
                time.sleep(1)
            result = mod.get_results()
            print(json.dumps(result, ensure_ascii=False, indent=2))

        elif action == "results":
            result = mod.get_results()
            print(json.dumps(result, ensure_ascii=False, indent=2))

        elif action == "cleanup":
            result = mod.cleanup()
            print(json.dumps(result, ensure_ascii=False, indent=2))

        else:
            print(f"未知动作: {action}")
            print("用法: python3 jetson_fuzz_modules.py <module_name> <setup|status|fuzz|results|cleanup>")
    else:
        print("用法:")
        print("  python3 jetson_fuzz_modules.py list                          # 列出所有模块")
        print("  python3 jetson_fuzz_modules.py <module> setup                # 设置模块环境")
        print("  python3 jetson_fuzz_modules.py <module> status               # 查看模块状态")
        print("  python3 jetson_fuzz_modules.py <module> fuzz [count]         # 运行模糊测试")
        print("  python3 jetson_fuzz_modules.py <module> results              # 查看测试结果")
        print("  python3 jetson_fuzz_modules.py <module> cleanup              # 清理环境")
        print("\n可用模块:")
        for m in MODULE_LIST:
            print(f"  {m['name']:20s} - {m['description']}")
