#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
2520 - Jetson端轻量级Agent
运行于Jetson (ARM64/Linux)，由PC端Run_2520.py通过HTTP控制：
1. 接收PC端上传的样本文件 (HTTP multipart)
2. 在Jetson本地对ARM64 Linux目标执行模糊测试
3. 通过Linux信号处理检测崩溃 (SIGSEGV, SIGABRT, SIGBUS等)
4. 向PC端报告状态和崩溃记录
纯Python3标准库实现，无第三方依赖
"""

import os, sys, json, uuid, time, glob, shutil, subprocess, threading
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse
from datetime import datetime

# 导入Jetson独有Fuzz模块
try:
    from jetson_fuzz_modules import MODULES
    MODULES_AVAILABLE = True
except ImportError as e:
    MODULES = {}
    MODULES_AVAILABLE = False
    print(f"[!] jetson_fuzz_modules 未加载: {e}")

# ==================== 常量定义 ====================
HOST = "0.0.0.0"
PORT = 7886
CRASH_DIR = "./crash_records"
WORKSPACE = "/home/nvidia/2520Fuzz/fuzz_workspace"
MAX_UPLOAD_SIZE = 100 * 1024 * 1024  # 最大上传100MB
start_time = time.time()

# Linux信号映射表
SIGNAL_MAP = {
    1: "SIGHUP", 2: "SIGINT", 3: "SIGQUIT", 4: "SIGILL",
    5: "SIGTRAP", 6: "SIGABRT", 7: "SIGBUS", 8: "SIGFPE",
    9: "SIGKILL", 11: "SIGSEGV", 13: "SIGPIPE", 14: "SIGALRM",
    15: "SIGTERM", 31: "SIGSYS"
}
# Shell退出码映射 (128+signum)
SHELL_EXIT_MAP = {
    139: "SIGSEGV", 134: "SIGABRT", 135: "SIGBUS",
    136: "SIGFPE", 132: "SIGILL",
}

# ==================== 全局模糊测试状态 ====================
fuzz_state = {
    "running": False, "target_exe": "", "sample_dir": "",
    "sample_list": [], "current_index": 0, "crashes": [],
    "total_run": 0, "process": None, "stop_flag": False,
    "log": [], "timeout": 30, "args": ""
}
fuzz_lock = threading.Lock()

# ==================== 模块引擎状态 ====================
module_instances = {}
module_lock = threading.Lock()

# ==================== 信号解析 ====================
def interpret_return_code(return_code):
    """解析进程退出码，返回(信号名, 是否崩溃)"""
    if return_code is None:
        return "UNKNOWN", True
    if return_code == 0:
        return None, False
    # 被信号杀死 (returncode < 0)
    if return_code < 0:
        sig_num = -return_code
        return SIGNAL_MAP.get(sig_num, f"SIG{sig_num}"), True
    # Shell退出码 (128 + signum)
    if return_code in SHELL_EXIT_MAP:
        return SHELL_EXIT_MAP[return_code], True
    if return_code > 128:
        sig_num = return_code - 128
        if sig_num in SIGNAL_MAP:
            return SIGNAL_MAP[sig_num], True
    # 退出码1通常表示普通错误，不算崩溃
    if return_code == 1:
        return None, False
    return f"EXIT_{return_code}", True

# ==================== 崩溃记录管理 ====================
def generate_crash_id():
    """生成崩溃记录ID: 时间戳+随机后缀"""
    return datetime.now().strftime("%Y%m%d_%H%M%S_") + uuid.uuid4().hex[:6]

def save_crash_record(crash_info):
    """保存崩溃记录到JSON文件"""
    os.makedirs(CRASH_DIR, exist_ok=True)
    crash_id = crash_info.get("crash_id", generate_crash_id())
    crash_info["crash_id"] = crash_id
    path = os.path.join(CRASH_DIR, f"{crash_id}.json")
    try:
        with open(path, "w", encoding="utf-8") as f:
            json.dump(crash_info, f, ensure_ascii=False, indent=2)
    except Exception as e:
        print(f"[崩溃记录] 保存失败: {e}")

def load_crash_records():
    """加载所有崩溃记录，按时间倒序排列"""
    records = []
    if not os.path.exists(CRASH_DIR):
        return records
    for fp in glob.glob(os.path.join(CRASH_DIR, "*.json")):
        try:
            with open(fp, "r", encoding="utf-8") as f:
                records.append(json.load(f))
        except Exception:
            pass
    records.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
    return records

def get_crash_record(crash_id):
    """获取指定崩溃记录"""
    path = os.path.join(CRASH_DIR, f"{crash_id}.json")
    if not os.path.exists(path):
        return None
    try:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return None

def delete_crash_record(crash_id):
    """删除指定崩溃记录"""
    path = os.path.join(CRASH_DIR, f"{crash_id}.json")
    if os.path.exists(path):
        os.remove(path)
        return True
    return False

# ==================== 辅助函数 ====================
def get_disk_free():
    """获取当前磁盘剩余空间(MB)"""
    try:
        usage = shutil.disk_usage("/")
        return round(usage.free / (1024 * 1024), 1)
    except Exception:
        return -1

def get_agent_status():
    """获取Agent状态信息"""
    return {
        "version": "1.0", "platform": "jetson-arm64",
        "uptime": round(time.time() - start_time, 1),
        "fuzz_running": fuzz_state["running"],
        "crash_count": len(load_crash_records()),
        "disk_free": get_disk_free(), "python": sys.version
    }

# ==================== 模糊测试引擎 ====================
def fuzz_worker():
    """模糊测试工作线程：逐个载入样本，运行目标程序，检测崩溃"""
    global fuzz_state
    while True:
        with fuzz_lock:
            # 检查停止标志
            if not fuzz_state["running"] or fuzz_state["stop_flag"]:
                fuzz_state["running"] = False
                fuzz_state["stop_flag"] = False
                return
            if fuzz_state["current_index"] >= len(fuzz_state["sample_list"]):
                fuzz_state["running"] = False
                fuzz_state["log"].append(
                    f"[{datetime.now().isoformat()}] 所有样本已测试完成，"
                    f"共发现 {len(fuzz_state['crashes'])} 个崩溃")
                return
            sample_path = fuzz_state["sample_list"][fuzz_state["current_index"]]
            fuzz_state["current_index"] += 1
            target_exe = fuzz_state["target_exe"]
            timeout = fuzz_state.get("timeout", 30)
            args_str = fuzz_state.get("args", "")

        sample_name = os.path.basename(sample_path)
        idx = fuzz_state["current_index"]
        total = len(fuzz_state["sample_list"])
        fuzz_state["log"].append(
            f"[{datetime.now().isoformat()}] 测试样本 {idx}/{total}: {sample_name}")

        try:
            # 构建命令行：target_exe [args] sample_path
            cmd = [target_exe]
            if args_str.strip():
                cmd.extend(args_str.strip().split())
            cmd.append(sample_path)
            t0 = time.time()
            # Linux下运行子进程，不使用creationflags
            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            fuzz_state["process"] = proc
            try:
                return_code = proc.wait(timeout=timeout)
                elapsed = round(time.time() - t0, 2)
            except subprocess.TimeoutExpired:
                proc.kill()
                proc.wait()
                return_code = -9  # SIGKILL
                elapsed = timeout

            fuzz_state["total_run"] += 1
            sig_name, is_crash = interpret_return_code(return_code)

            if is_crash and sig_name:
                # 记录崩溃信息
                crash_info = {
                    "crash_id": generate_crash_id(),
                    "sample_path": sample_path, "sample_name": sample_name,
                    "target_exe": target_exe, "signal": sig_name,
                    "return_code": return_code, "elapsed": elapsed,
                    "timestamp": datetime.now().isoformat(),
                    "index": fuzz_state["total_run"]
                }
                fuzz_state["crashes"].append(crash_info)
                save_crash_record(crash_info)
                fuzz_state["log"].append(
                    f"[{datetime.now().isoformat()}] *** 崩溃检测 *** "
                    f"样本: {sample_name}, 信号: {sig_name}, "
                    f"退出码: {return_code}, 耗时: {elapsed}s")
            elif return_code == 0:
                fuzz_state["log"].append(
                    f"[{datetime.now().isoformat()}] 样本正常 (exit=0, {elapsed}s): {sample_name}")
            else:
                fuzz_state["log"].append(
                    f"[{datetime.now().isoformat()}] 样本异常退出 (exit={return_code}, {elapsed}s): {sample_name}")
        except FileNotFoundError:
            fuzz_state["log"].append(f"[{datetime.now().isoformat()}] 错误: 目标程序不存在 - {target_exe}")
            fuzz_state["total_run"] += 1
        except PermissionError:
            fuzz_state["log"].append(f"[{datetime.now().isoformat()}] 错误: 无执行权限 - {target_exe}")
            fuzz_state["total_run"] += 1
        except Exception as e:
            fuzz_state["log"].append(f"[{datetime.now().isoformat()}] 运行异常: {e}")
            fuzz_state["total_run"] += 1
        time.sleep(0.05)  # 短暂间隔，避免CPU占用过高

# ==================== HTTP请求处理器 ====================
class JetsonAgentHandler(BaseHTTPRequestHandler):
    """Jetson Agent HTTP请求处理器"""

    def log_message(self, format, *args):
        print(f"[{self.address_string()}] {format % args}")

    def _send_json(self, data, status=200):
        """发送JSON响应，启用CORS"""
        content = json.dumps(data, ensure_ascii=False).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", len(content))
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        self.end_headers()
        self.wfile.write(content)

    def _read_body(self):
        """读取请求体"""
        content_length = int(self.headers.get("Content-Length", 0))
        if content_length > MAX_UPLOAD_SIZE:
            return None
        return self.rfile.read(content_length)

    def _read_body_json(self):
        """读取请求体并解析为JSON"""
        content_length = int(self.headers.get("Content-Length", 0))
        if content_length > 1024 * 1024:
            return None
        body = self.rfile.read(content_length)
        try:
            return json.loads(body.decode("utf-8"))
        except (json.JSONDecodeError, UnicodeDecodeError):
            return None

    def _parse_multipart(self, content_type, body):
        """手动解析multipart/form-data请求体"""
        # 从Content-Type头提取boundary
        boundary = content_type.split("boundary=")[1].strip()
        if boundary.startswith('"') and boundary.endswith('"'):
            boundary = boundary[1:-1]
        parts = body.split(b'--' + boundary.encode())
        result = {}
        for part in parts:
            if part in (b'', b'--\r\n', b'--'):
                continue
            if part.startswith(b'\r\n'):
                part = part[2:]
            if part.endswith(b'\r\n'):
                part = part[:-2]
            # 分离头部和正文
            header_end = part.find(b'\r\n\r\n')
            if header_end == -1:
                continue
            headers_raw = part[:header_end].decode('utf-8', errors='replace')
            body_data = part[header_end + 4:]
            if body_data.endswith(b'\r\n'):
                body_data = body_data[:-2]
            # 解析Content-Disposition
            name = filename = None
            for header_line in headers_raw.split('\r\n'):
                if 'Content-Disposition' in header_line:
                    for item in header_line.split(';'):
                        item = item.strip()
                        if item.startswith('name='):
                            name = item[5:].strip('"')
                        elif item.startswith('filename='):
                            filename = item[9:].strip('"')
            if name:
                if filename:
                    result[name] = {'filename': filename, 'data': body_data}
                else:
                    result[name] = body_data.decode('utf-8', errors='replace')
        return result

    # ==================== CORS预检 ====================
    def do_OPTIONS(self):
        self.send_response(200)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        self.end_headers()

    # ==================== GET路由 ====================
    def do_GET(self):
        path = urlparse(self.path).path
        if path == "/api/status":
            self._send_json(get_agent_status())
        elif path == "/api/fuzz/status":
            self._handle_fuzz_status()
        elif path == "/api/crashes":
            self._handle_list_crashes()
        elif path.startswith("/api/crashes/"):
            self._handle_get_crash(path[len("/api/crashes/"):])
        elif path == "/api/targets":
            self._handle_list_targets()
        elif path == "/api/samples":
            self._handle_list_samples()
        elif path.startswith("/api/module/"):
            self._handle_module_get(path)
        else:
            self._send_json({"error": "Not found"}, 404)

    # ==================== POST路由 ====================
    def do_POST(self):
        path = urlparse(self.path).path
        if path == "/api/upload":
            self._handle_upload()
        elif path == "/api/fuzz/start":
            self._handle_fuzz_start()
        elif path == "/api/fuzz/stop":
            self._handle_fuzz_stop()
        elif path.startswith("/api/module/"):
            self._handle_module_post(path)
        else:
            self._send_json({"error": "Not found"}, 404)

    # ==================== DELETE路由 ====================
    def do_DELETE(self):
        path = urlparse(self.path).path
        if path.startswith("/api/crashes/"):
            self._handle_delete_crash(path[len("/api/crashes/"):])
        elif path.startswith("/api/module/"):
            self._handle_module_delete(path)
        else:
            self._send_json({"error": "Not found"}, 404)

    # ==================== 文件上传接口 ====================
    def _handle_upload(self):
        """处理文件上传 (multipart/form-data)"""
        content_type = self.headers.get("Content-Type", "")
        if "multipart/form-data" not in content_type:
            self._send_json({"error": "需要multipart/form-data格式"}, 400)
            return
        body = self._read_body()
        if body is None:
            self._send_json({"error": "文件过大，超过100MB限制"}, 413)
            return
        try:
            parsed = self._parse_multipart(content_type, body)
        except Exception as e:
            self._send_json({"error": f"解析multipart数据失败: {e}"}, 400)
            return
        file_part = parsed.get("file")
        remote_dir = parsed.get("remote_dir", WORKSPACE)
        if not file_part or not isinstance(file_part, dict):
            self._send_json({"error": "缺少file字段"}, 400)
            return
        filename = os.path.basename(file_part.get("filename") or "upload.bin")
        file_data = file_part.get("data", b"")
        # 创建目标目录并写入文件
        os.makedirs(remote_dir, exist_ok=True)
        save_path = os.path.join(remote_dir, filename)
        try:
            with open(save_path, "wb") as f:
                f.write(file_data)
        except Exception as e:
            self._send_json({"error": f"文件写入失败: {e}"}, 500)
            return
        self._send_json({"success": True, "path": save_path, "size": len(file_data)})

    # ==================== 模糊测试接口 ====================
    def _handle_fuzz_start(self):
        """启动本地模糊测试"""
        global fuzz_state
        params = self._read_body_json()
        if params is None:
            self._send_json({"error": "无效的JSON请求"}, 400)
            return
        with fuzz_lock:
            if fuzz_state["running"]:
                self._send_json({"error": "模糊测试已在运行中"}, 400)
                return
            target_exe = params.get("target_exe", "")
            sample_dir = params.get("sample_dir", "")
            timeout_val = params.get("timeout", 30)
            args_str = params.get("args", "")
            # 验证目标程序
            if not target_exe:
                self._send_json({"error": "未指定目标程序"}, 400)
                return
            if not os.path.exists(target_exe):
                self._send_json({"error": f"目标程序不存在: {target_exe}"}, 400)
                return
            if not os.access(target_exe, os.X_OK):
                self._send_json({"error": f"目标程序无执行权限: {target_exe}"}, 400)
                return
            # 验证样本目录
            if not sample_dir:
                self._send_json({"error": "未指定样本目录"}, 400)
                return
            if not os.path.isdir(sample_dir):
                self._send_json({"error": f"样本目录不存在: {sample_dir}"}, 400)
                return
            # 收集样本文件
            sample_list = sorted([
                os.path.join(sample_dir, fn)
                for fn in os.listdir(sample_dir)
                if os.path.isfile(os.path.join(sample_dir, fn))
            ])
            if not sample_list:
                self._send_json({"error": "样本目录中无文件"}, 400)
                return
            # 初始化模糊测试状态
            fuzz_state = {
                "running": True, "target_exe": target_exe,
                "sample_dir": sample_dir, "sample_list": sample_list,
                "current_index": 0, "crashes": [], "total_run": 0,
                "process": None, "stop_flag": False,
                "log": [f"[{datetime.now().isoformat()}] 模糊测试启动: "
                        f"{len(sample_list)}个样本, 目标={os.path.basename(target_exe)}, "
                        f"超时={timeout_val}s"],
                "timeout": timeout_val, "args": args_str
            }
        # 启动工作线程
        threading.Thread(target=fuzz_worker, daemon=True).start()
        self._send_json({"success": True, "message": f"模糊测试已启动，共{len(sample_list)}个样本"})

    def _handle_fuzz_stop(self):
        """停止本地模糊测试"""
        with fuzz_lock:
            if not fuzz_state["running"]:
                self._send_json({"error": "模糊测试未在运行"}, 400)
                return
            fuzz_state["stop_flag"] = True
            if fuzz_state["process"]:
                try:
                    fuzz_state["process"].kill()
                except Exception:
                    pass
            fuzz_state["log"].append(f"[{datetime.now().isoformat()}] 用户手动停止模糊测试")
        self._send_json({"success": True, "message": "模糊测试正在停止..."})

    def _handle_fuzz_status(self):
        """获取模糊测试状态"""
        with fuzz_lock:
            state_copy = {
                "running": fuzz_state["running"],
                "target_exe": fuzz_state["target_exe"],
                "sample_dir": fuzz_state["sample_dir"],
                "total_samples": len(fuzz_state["sample_list"]),
                "current_index": fuzz_state["current_index"],
                "crashes": list(fuzz_state["crashes"]),
                "total_run": fuzz_state["total_run"],
                "log": fuzz_state["log"][-100:],
                "timeout": fuzz_state.get("timeout", 30),
                "args": fuzz_state.get("args", "")
            }
        self._send_json(state_copy)

    # ==================== 崩溃记录接口 ====================
    def _handle_list_crashes(self):
        records = load_crash_records()
        self._send_json({"crashes": records, "total": len(records)})

    def _handle_get_crash(self, crash_id):
        record = get_crash_record(crash_id)
        if record is None:
            self._send_json({"error": "崩溃记录不存在"}, 404)
        else:
            self._send_json(record)

    def _handle_delete_crash(self, crash_id):
        if delete_crash_record(crash_id):
            self._send_json({"success": True, "message": "崩溃记录已删除"})
        else:
            self._send_json({"error": "崩溃记录不存在"}, 404)

    # ==================== 目标与样本接口 ====================
    def _handle_list_targets(self):
        """列出常见路径下的可执行文件"""
        search_paths = ["/usr/local/bin", "/usr/bin", "/home"]
        targets = []
        for sp in search_paths:
            if not os.path.exists(sp):
                continue
            try:
                for entry in os.listdir(sp):
                    full = os.path.join(sp, entry)
                    if os.path.isfile(full) and os.access(full, os.X_OK):
                        targets.append({"name": entry, "path": full})
            except PermissionError:
                continue
        # 去重
        seen = set()
        unique = []
        for t in targets:
            if t["path"] not in seen:
                seen.add(t["path"])
                unique.append(t)
        self._send_json({"targets": unique})

    def _handle_list_samples(self):
        """列出fuzz_workspace下的样本目录"""
        if not os.path.exists(WORKSPACE):
            self._send_json({"samples": [], "workspace": WORKSPACE})
            return
        dirs = []
        for entry in sorted(os.listdir(WORKSPACE)):
            full = os.path.join(WORKSPACE, entry)
            if os.path.isdir(full):
                try:
                    fc = sum(1 for f in os.listdir(full) if os.path.isfile(os.path.join(full, f)))
                except PermissionError:
                    fc = 0
                dirs.append({"name": entry, "path": full, "file_count": fc})
        self._send_json({"samples": dirs, "workspace": WORKSPACE})

    # ==================== Jetson独有Fuzz模块引擎 ====================
    
    def _get_module_instance(self, module_name):
        """获取或创建模块实例，支持短名映射"""
        # 模块名映射 (前端短名 -> 后端注册名)
        name_map = {
            "can": "can_fuzz",
            "camera": "camera_fuzz",
            "serial": "serial_fuzz",
            "i2c": "i2c_sensor_fuzz",
            "ollama": "ollama_fuzz",
            "network": "network_fuzz",
        }
        full_name = name_map.get(module_name, module_name)
        with module_lock:
            if full_name not in module_instances:
                if full_name not in MODULES:
                    return None
                module_instances[full_name] = MODULES[full_name]
            return module_instances[full_name]
    
    def _handle_module_get(self, path):
        """处理模块GET请求"""
        parts = path.strip("/").split("/")
        # /api/module/{name}/status  ->  ['api', 'module', 'name', 'status']
        # /api/module/{name}/results ->  ['api', 'module', 'name', 'results']
        if len(parts) < 3:
            self._send_json({"error": "path错误"}, 400)
            return
        module_name = parts[2]
        action = parts[3] if len(parts) > 3 else "status"
        
        module = self._get_module_instance(module_name)
        if not module:
            self._send_json({"error": f"模块 '{module_name}' 不存在", "available": list(MODULES.keys())}, 404)
            return
        
        try:
            if action == "status":
                data = module.get_status()
                # 补充模块元信息
                data["name"] = module.name
                data["description"] = module.description
                self._send_json(data)
            elif action == "results":
                data = module.get_results()
                self._send_json(data)
            else:
                self._send_json({"error": f"不支持的操作: {action}"}, 400)
        except Exception as e:
            self._send_json({"error": str(e)}, 500)
    
    def _handle_module_post(self, path):
        """处理模块POST请求"""
        parts = path.strip("/").split("/")
        # /api/module/{name}/setup   ->  ['api', 'module', 'name', 'setup']
        # /api/module/{name}/start   ->  ['api', 'module', 'name', 'start']
        if len(parts) < 3:
            self._send_json({"error": "path错误"}, 400)
            return
        module_name = parts[2]
        action = parts[3] if len(parts) > 3 else ""
        
        module = self._get_module_instance(module_name)
        if not module:
            self._send_json({"error": f"模块 '{module_name}' 不存在", "available": list(MODULES.keys())}, 404)
            return
        
        # 读取可选的请求体参数
        params = {}
        try:
            body = self._read_body()
            if body:
                params = json.loads(body.decode("utf-8"))
        except:
            params = {}
        
        try:
            if action == "setup":
                data = module.setup()
            elif action == "cleanup":
                data = module.cleanup()
                # 清理后移除实例
                with module_lock:
                    module_instances.pop(module_name, None)
            elif action == "start":
                data = module.start_fuzz(params)
            elif action == "stop":
                data = module.stop_fuzz()
            else:
                self._send_json({"error": f"不支持的操作: {action}"}, 400)
                return
            self._send_json(data)
        except Exception as e:
            self._send_json({"error": str(e), "module": module_name, "action": action}, 500)
    
    def _handle_module_delete(self, path):
        """处理模块DELETE请求 - 清理模块"""
        parts = path.strip("/").split("/")
        # /api/module/{name}  ->  ['api', 'module', 'name']
        if len(parts) < 3:
            self._send_json({"error": "path错误"}, 400)
            return
        module_name = parts[2]
        name_map = {"can":"can_fuzz","camera":"camera_fuzz","serial":"serial_fuzz",
                    "i2c":"i2c_sensor_fuzz","ollama":"ollama_fuzz","network":"network_fuzz"}
        full_name = name_map.get(module_name, module_name)

        with module_lock:
            if full_name in module_instances:
                try:
                    module_instances[full_name].cleanup()
                except:
                    pass
                del module_instances[full_name]
                self._send_json({"success": True, "message": f"模块 '{module_name}' 已清理"})
            else:
                self._send_json({"success": True, "message": f"模块 '{module_name}' 未加载"})

# ==================== 服务器启动 ====================
if __name__ == "__main__":
    port = PORT
    # 允许通过命令行参数覆盖端口
    if len(sys.argv) > 1:
        port = int(sys.argv[1])
    os.makedirs(CRASH_DIR, exist_ok=True)
    os.makedirs(WORKSPACE, exist_ok=True)
    server = HTTPServer(("0.0.0.0", port), JetsonAgentHandler)
    print(f"2520 Jetson Agent running on http://0.0.0.0:{port}/")
    print("Waiting for commands from PC...")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nShutting down...")
        # 清理模糊测试进程
        with fuzz_lock:
            if fuzz_state["running"]:
                fuzz_state["stop_flag"] = True
                if fuzz_state["process"]:
                    try:
                        fuzz_state["process"].kill()
                    except Exception:
                        pass
        server.shutdown()
