From 1189dbdbcc319dc45b047ccb48b48680e3f3bd63 Mon Sep 17 00:00:00 2001 From: HopeLi <1278288511@qq.com> Date: Thu, 3 Jul 2025 20:56:37 +0800 Subject: [PATCH] =?UTF-8?q?0703=20ljc=20=20=20afl=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=93=8D=E4=BD=9C=E4=BB=A5=E5=8F=8A=E5=85=A5?= =?UTF-8?q?=E5=BA=93=E6=93=8D=E4=BD=9C=E4=BB=A5=E5=8F=8A=E6=97=B6=E5=BA=8F?= =?UTF-8?q?=E8=A1=A8=E6=93=8D=E4=BD=9C=E7=AD=89=E6=8E=A5=E5=8F=A3=E5=BC=80?= =?UTF-8?q?=E5=8F=91=EF=BC=8C=E5=8C=85=E5=90=AB=E5=88=86=E7=89=87=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=BB=A5=E5=8F=8A=E6=96=87=E4=BB=B6=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B1=BB=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cd/casic/ci/api/AflManagerController.java | 73 ++ .../dto/req/aflManager/AflManagerReq.java | 66 ++ .../dto/resp/aflManager/SeedsCountResp.java | 19 + .../dao/aflManager/AflCrashesInfoDao.java | 14 + .../dao/aflManager/AflFileChunkDao.java | 14 + .../process/dao/aflManager/AflInfoDao.java | 14 + .../dao/aflManager/AflSeedInfoDao.java | 14 + .../dataObject/aflManager/AflCrashesInfo.java | 26 + .../dataObject/aflManager/AflFileChunk.java | 39 + .../dataObject/aflManager/AflInfo.java | 270 +++++++ .../dataObject/aflManager/AflSeedInfo.java | 32 + .../aflManager/AflCrashesInfoService.java | 18 + .../aflManager/AflFileChunkService.java | 20 + .../service/aflManager/AflInfoService.java | 19 + .../aflManager/AflSeedInfoService.java | 24 + .../impl/AflCrashesInfoServiceImpl.java | 66 ++ .../impl/AflFileChunkServiceImpl.java | 104 +++ .../aflManager/impl/AflInfoServiceImpl.java | 76 ++ .../impl/AflSeedInfoServiceImpl.java | 189 +++++ .../casic/ci/process/util/SftpUploadUtil.java | 692 +++++++++++++++++- 20 files changed, 1787 insertions(+), 2 deletions(-) create mode 100644 modules/module-ci-process-api/src/main/java/cd/casic/ci/api/AflManagerController.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/dto/req/aflManager/AflManagerReq.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/dto/resp/aflManager/SeedsCountResp.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflCrashesInfoDao.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflFileChunkDao.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflInfoDao.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflSeedInfoDao.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflCrashesInfo.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflFileChunk.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflInfo.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflSeedInfo.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflCrashesInfoService.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflFileChunkService.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflInfoService.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflSeedInfoService.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflCrashesInfoServiceImpl.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflFileChunkServiceImpl.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflInfoServiceImpl.java create mode 100644 modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflSeedInfoServiceImpl.java diff --git a/modules/module-ci-process-api/src/main/java/cd/casic/ci/api/AflManagerController.java b/modules/module-ci-process-api/src/main/java/cd/casic/ci/api/AflManagerController.java new file mode 100644 index 00000000..10cb1da2 --- /dev/null +++ b/modules/module-ci-process-api/src/main/java/cd/casic/ci/api/AflManagerController.java @@ -0,0 +1,73 @@ +package cd.casic.ci.api; + +import cd.casic.ci.process.dto.req.aflManager.AflManagerReq; +import cd.casic.ci.process.dto.resp.aflManager.SeedsCountResp; +import cd.casic.ci.process.process.service.aflManager.AflCrashesInfoService; +import cd.casic.ci.process.process.service.aflManager.AflInfoService; +import cd.casic.ci.process.process.service.aflManager.AflSeedInfoService; +import cd.casic.ci.process.util.SftpUploadUtil; +import cd.casic.framework.commons.pojo.CommonResult; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName AflInfoController + * @Date: 2025/7/1 9:03 + * @Description: + */ +@RestController +@RequestMapping("/aflManager") +public class AflManagerController { + + @Resource + private AflInfoService aflInfoService; + + @Resource + private AflCrashesInfoService aflCrashesInfoService; + + @Resource + private AflSeedInfoService aflSeedInfoService; + + @PostMapping(path="/saveAflInfo") + public CommonResult saveAflInfo(@RequestBody @Valid AflManagerReq req) throws SftpUploadUtil.SftpUploadException { + + String aflInfoId = aflInfoService.saveAflInfo(req); + + return CommonResult.success(aflInfoId); + } + + + @PostMapping(path="/saveAflCrashesInfo") + public CommonResult saveAflCrashesInfo(@RequestBody @Valid AflManagerReq req) throws SftpUploadUtil.SftpUploadException { + + aflCrashesInfoService.saveAflCrashesInfo(req); + + return CommonResult.success(); + } + + + @PostMapping(path="/findSeedsCount") + public CommonResult> findSeedsCount(@RequestBody @Valid AflManagerReq req) throws SftpUploadUtil.SftpUploadException { + + List seedsCount = aflSeedInfoService.findSeedsCount(req); + + return CommonResult.success(seedsCount); + } + + + @PostMapping(path="/saveAflSeedInfo") + public CommonResult saveAflSeedInfo(@RequestBody @Valid AflManagerReq req) throws SftpUploadUtil.SftpUploadException { + + aflSeedInfoService.saveAflSeedInfo(req); + + return CommonResult.success(); + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/dto/req/aflManager/AflManagerReq.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/dto/req/aflManager/AflManagerReq.java new file mode 100644 index 00000000..f25329c8 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/dto/req/aflManager/AflManagerReq.java @@ -0,0 +1,66 @@ +package cd.casic.ci.process.dto.req.aflManager; + +import lombok.Data; + +@Data +public class AflManagerReq { + private String pipelineId; + private String pipelineHistoryId; + private String taskId; + + /** + * 主机地址 + */ + private String machineHost; + + /** + * 描述 + */ + private String description; + + /** + * 机器状态 + */ + private String machineStatus; + + /** + * 登录用户名 + */ + private String username; + + /** + * SSH 端口 + */ + private String sshPort; + + /** + * 密码 + */ + private String password; + + /** + * 秘钥 ID + */ + private Long secretKeyId; + + /** + * 代理 ID + */ + private Long proxyId; + + /** + * 认证类型编码 + */ + private Integer authenticationTypeCode; + + /** + * 认证类型 + */ + private String authType; + + /** + * 操作系统 + */ + private String osSystem; + +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/dto/resp/aflManager/SeedsCountResp.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/dto/resp/aflManager/SeedsCountResp.java new file mode 100644 index 00000000..52f92b86 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/dto/resp/aflManager/SeedsCountResp.java @@ -0,0 +1,19 @@ +package cd.casic.ci.process.dto.resp.aflManager; + +import lombok.Data; + +/** + * @ClassName ReportResp + * @Author hopeli + * @Date 2025/5/10 10:53 + * @Version 1.0 + */ +@Data +public class SeedsCountResp { + private Integer repValue; + + private Integer minutesDifference; + + private String seedId; + +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflCrashesInfoDao.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflCrashesInfoDao.java new file mode 100644 index 00000000..1ec85c70 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflCrashesInfoDao.java @@ -0,0 +1,14 @@ +package cd.casic.ci.process.process.dao.aflManager; + +import cd.casic.ci.process.process.dataObject.aflManager.AflCrashesInfo; +import cd.casic.framework.mybatis.core.mapper.BaseMapperX; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName AflInfoDao + * @Date: 2025/5/13 14:39 + * @Description: + */ +public interface AflCrashesInfoDao extends BaseMapperX { +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflFileChunkDao.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflFileChunkDao.java new file mode 100644 index 00000000..03d9f5cd --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflFileChunkDao.java @@ -0,0 +1,14 @@ +package cd.casic.ci.process.process.dao.aflManager; + +import cd.casic.ci.process.process.dataObject.aflManager.AflFileChunk; +import cd.casic.framework.mybatis.core.mapper.BaseMapperX; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName AflInfoDao + * @Date: 2025/5/13 14:39 + * @Description: + */ +public interface AflFileChunkDao extends BaseMapperX { +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflInfoDao.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflInfoDao.java new file mode 100644 index 00000000..ea21f354 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflInfoDao.java @@ -0,0 +1,14 @@ +package cd.casic.ci.process.process.dao.aflManager; + +import cd.casic.ci.process.process.dataObject.aflManager.AflInfo; +import cd.casic.framework.mybatis.core.mapper.BaseMapperX; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName AflInfoDao + * @Date: 2025/5/13 14:39 + * @Description: + */ +public interface AflInfoDao extends BaseMapperX { +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflSeedInfoDao.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflSeedInfoDao.java new file mode 100644 index 00000000..a9059208 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dao/aflManager/AflSeedInfoDao.java @@ -0,0 +1,14 @@ +package cd.casic.ci.process.process.dao.aflManager; + +import cd.casic.ci.process.process.dataObject.aflManager.AflSeedInfo; +import cd.casic.framework.mybatis.core.mapper.BaseMapperX; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName AflInfoDao + * @Date: 2025/5/13 14:39 + * @Description: + */ +public interface AflSeedInfoDao extends BaseMapperX { +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflCrashesInfo.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflCrashesInfo.java new file mode 100644 index 00000000..1895ac04 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflCrashesInfo.java @@ -0,0 +1,26 @@ +package cd.casic.ci.process.process.dataObject.aflManager; + +import cd.casic.ci.process.process.dataObject.base.PipBaseElement; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("afl_crashes_info") +public class AflCrashesInfo extends PipBaseElement { + + /** + * 流水线id + */ + private String pipelineId; + + private String pipelineHistoryId; + private String taskId; + + + /** + * 文件地址路径 + */ + private String filePath; +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflFileChunk.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflFileChunk.java new file mode 100644 index 00000000..1bb24992 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflFileChunk.java @@ -0,0 +1,39 @@ +package cd.casic.ci.process.process.dataObject.aflManager; + +import cd.casic.ci.process.process.dataObject.base.PipBaseElement; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName FileChunk + * @Date: 2025/7/1 16:03 + * @Description: + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("afl_file_chunk") +public class AflFileChunk extends PipBaseElement { + + /** + * 文件唯一标识 ID + */ + private String contentId; + + /** + * 分片索引,从0开始计数 + */ + private Integer chunkIndex; + + /** + * 总分片数量 + */ + private Integer chunksSum; + + /** + * 分片内容(二进制数据) + */ + private byte[] chunkContent; +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflInfo.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflInfo.java new file mode 100644 index 00000000..d5d65c5b --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflInfo.java @@ -0,0 +1,270 @@ +package cd.casic.ci.process.process.dataObject.aflManager; + +import cd.casic.ci.process.process.dataObject.base.PipBaseElement; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("afl_info") +public class AflInfo extends PipBaseElement { + private String pipelineId; + private String pipelineHistoryId; + private String taskId; + + /** + * 模糊测试进程的开始时间 + */ + private String startTime; + + /** + * 最后一次更新时间戳 + */ + private String lastUpdate; + + /** + * 总运行时间 + */ + private String runTime; + + /** + * 模糊器的进程 ID + */ + private String fuzzerPid; + + /** + * 已完成的循环次数 + */ + private String cyclesDone; + + /** + * 未发现新内容的循环次数 + */ + private String cyclesWoFinds; + + /** + * 未发现新内容的时间 + */ + private String timeWoFinds; + + /** + * 模糊测试所用时间 + */ + private String fuzzTime; + + /** + * 校准时间 + */ + private String calibrationTime; + + /** + * 比较日志时间 + */ + private String cmplogTime; + + /** + * 同步时间 + */ + private String syncTime; + + /** + * 裁剪时间 + */ + private String trimTime; + + /** + * 已执行的次数 + */ + private String execsDone; + + /** + * 每秒执行次数 + */ + private String execsPerSec; + + /** + * 上一分钟内的每秒执行次数 + */ + private String execsPsLastMin; + + /** + * 语料库中的条目数量 + */ + private String corpusCount; + + /** + * 语料库中被优先选择的条目 + */ + private String corpusFavored; + + /** + * 语料库中发现的条目 + */ + private String corpusFound; + + /** + * 导入到语料库的条目 + */ + private String corpusImported; + + /** + * 语料库中的可变条目 + */ + private String corpusVariable; + + /** + * 达到的最大深度 + */ + private String maxDepth; + + /** + * 当前处理的条目索引 + */ + private String curItem; + + /** + * 待处理的优先条目数 + */ + private String pendingFavs; + + /** + * 总待处理条目数 + */ + private String pendingTotal; + + /** + * 稳定性百分比 + */ + private String stability; + + /** + * 位图覆盖率百分比 + */ + private String bitmapCvg; + + /** + * 保存的崩溃次数 + */ + private String savedCrashes; + + /** + * 保存的挂起次数 + */ + private String savedHangs; + + /** + * 总超时次数 + */ + private String totalTmout; + + /** + * 上次发现的时间戳 + */ + private String lastFind; + + /** + * 上次崩溃的时间戳 + */ + private String lastCrash; + + /** + * 上次挂起的时间戳 + */ + private String lastHang; + + /** + * 自上次崩溃以来的执行次数 + */ + private String execsSinceCrash; + + /** + * 执行超时值 + */ + private String execTimeout; + + /** + * 最慢执行时间(毫秒) + */ + private String slowestExecMs; + + /** + * 峰值 RSS 内存使用量(MB) + */ + private String peakRssMb; + + /** + * CPU 亲和性设置 + */ + private String cpuAffinity; + + /** + * 发现的边数(edges) + */ + private String edgesFound; + + /** + * 总可用边数(edges) + */ + private String totalEdges; + + /** + * 可变字节计数 + */ + private String varByteCount; + + /** + * Havoc 扩展因子 + */ + private String havocExpansion; + + /** + * 自动词典条目数 + */ + private String autoDictEntries; + + /** + * 测试缓存大小 + */ + private String testcacheSize; + + /** + * 测试缓存条目数 + */ + private String testcacheCount; + + /** + * 测试缓存驱逐次数 + */ + private String testcacheEvict; + + /** + * 目标程序 banner + */ + private String aflBanner; + + /** + * 使用的 AFL 版本 + */ + private String aflVersion; + + /** + * 目标模式配置 + */ + private String targetMode; + + /** + * 执行使用的命令行 + */ + private String commandLine; + + /** + * 文件二进制内容 + */ + private String contentId; + + /** + * 文件地址路径 + */ + private String filePath; +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflSeedInfo.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflSeedInfo.java new file mode 100644 index 00000000..345c63f7 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/aflManager/AflSeedInfo.java @@ -0,0 +1,32 @@ +package cd.casic.ci.process.process.dataObject.aflManager; + +import cd.casic.ci.process.process.dataObject.base.PipBaseElement; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("afl_seed_info") +public class AflSeedInfo extends PipBaseElement { + + /** + * 模糊测试进程的开始时间 + */ + private String seedId; + + private String pipelineId; + private String pipelineHistoryId; + private String taskId; + + /** + * 最后一次更新时间戳 + */ + private String seedAmount; + + /** + * 文件地址路径 + */ + private String filePath; + +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflCrashesInfoService.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflCrashesInfoService.java new file mode 100644 index 00000000..c5994064 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflCrashesInfoService.java @@ -0,0 +1,18 @@ +package cd.casic.ci.process.process.service.aflManager; + +import cd.casic.ci.process.dto.req.aflManager.AflManagerReq; +import cd.casic.ci.process.process.dataObject.aflManager.AflCrashesInfo; +import com.baomidou.mybatisplus.extension.service.IService; +import jakarta.validation.Valid; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName AflInfoService + * @Date: 2025/5/17 10:20 + * @Description: + */ +public interface AflCrashesInfoService extends IService { + + void saveAflCrashesInfo(@Valid AflManagerReq req); +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflFileChunkService.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflFileChunkService.java new file mode 100644 index 00000000..3d56e813 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflFileChunkService.java @@ -0,0 +1,20 @@ +package cd.casic.ci.process.process.service.aflManager; + +import cd.casic.ci.process.process.dataObject.aflManager.AflFileChunk; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName FileChunkRepository + * @Date: 2025/7/1 16:03 + * @Description: + */ +public interface AflFileChunkService extends IService { + byte[] getFileById(String contentId) throws IOException; + + String saveFileChunk(InputStream file); +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflInfoService.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflInfoService.java new file mode 100644 index 00000000..66e7d710 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflInfoService.java @@ -0,0 +1,19 @@ +package cd.casic.ci.process.process.service.aflManager; + +import cd.casic.ci.process.dto.req.aflManager.AflManagerReq; +import cd.casic.ci.process.process.dataObject.aflManager.AflInfo; +import cd.casic.ci.process.util.SftpUploadUtil; +import com.baomidou.mybatisplus.extension.service.IService; +import jakarta.validation.Valid; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName AflInfoService + * @Date: 2025/5/17 10:20 + * @Description: + */ +public interface AflInfoService extends IService { + + String saveAflInfo(@Valid AflManagerReq req) throws SftpUploadUtil.SftpUploadException; +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflSeedInfoService.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflSeedInfoService.java new file mode 100644 index 00000000..7857ff95 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/AflSeedInfoService.java @@ -0,0 +1,24 @@ +package cd.casic.ci.process.process.service.aflManager; + +import cd.casic.ci.process.dto.req.aflManager.AflManagerReq; +import cd.casic.ci.process.dto.resp.aflManager.SeedsCountResp; +import cd.casic.ci.process.process.dataObject.aflManager.AflSeedInfo; +import cd.casic.ci.process.util.SftpUploadUtil; +import com.baomidou.mybatisplus.extension.service.IService; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName AflInfoService + * @Date: 2025/5/17 10:20 + * @Description: + */ +public interface AflSeedInfoService extends IService { + + List findSeedsCount(@Valid AflManagerReq req) throws SftpUploadUtil.SftpUploadException; + + void saveAflSeedInfo(@Valid AflManagerReq req); +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflCrashesInfoServiceImpl.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflCrashesInfoServiceImpl.java new file mode 100644 index 00000000..19a50c54 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflCrashesInfoServiceImpl.java @@ -0,0 +1,66 @@ +package cd.casic.ci.process.process.service.aflManager.impl; + +import cd.casic.ci.process.dto.req.aflManager.AflManagerReq; +import cd.casic.ci.process.process.dao.aflManager.AflCrashesInfoDao; +import cd.casic.ci.process.process.dataObject.aflManager.AflCrashesInfo; +import cd.casic.ci.process.process.service.aflManager.AflCrashesInfoService; +import cd.casic.ci.process.util.SftpUploadUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName TargetVersionServiceImpl + * @Date: 2025/5/17 15:20 + * @Description: + */ +@Service +@Slf4j +public class AflCrashesInfoServiceImpl extends ServiceImpl implements AflCrashesInfoService { + + private static final String remoteFilePath = "/home/casic/706/yunqi/"; + private static final String crashesFilePath = "/ai_afl/default/crashes/"; + + + @Override + public void saveAflCrashesInfo(AflManagerReq req) { + try { + // 步骤1:列出源目录下的所有文件 + List files = SftpUploadUtil.listFilesInRemoteDirectory( + req.getMachineHost(), Integer.parseInt(req.getSshPort()), req.getUsername(), req.getPassword(), null, remoteFilePath + "PIP_" + req.getPipelineId() + crashesFilePath); + + if (!CollectionUtils.isEmpty(files)) { + // 步骤2:批量复制文件到目标目录 + List copiedFiles = SftpUploadUtil.copyRemoteFile( + req.getMachineHost(), Integer.parseInt(req.getSshPort()), req.getUsername(), req.getPassword(), null, files, remoteFilePath + "PIP_" + req.getPipelineId() + "/crashes_result/"); + System.out.println("共复制 " + copiedFiles.size() + " 个文件"); + + if (!CollectionUtils.isEmpty(copiedFiles)){ + List aflCrashesInfos = new ArrayList<>(0); + //新增数据到crashes表中 + copiedFiles.forEach(o->{ + AflCrashesInfo aflCrashesInfo = new AflCrashesInfo(); + aflCrashesInfo.setFilePath(o); + aflCrashesInfo.setPipelineId(req.getPipelineId()); + aflCrashesInfo.setTaskId(req.getTaskId()); + aflCrashesInfo.setPipelineHistoryId(req.getPipelineHistoryId()); + aflCrashesInfos.add(aflCrashesInfo); + }); + baseMapper.insertBatch(aflCrashesInfos); + } + + // 步骤3:删除旧目录 + SftpUploadUtil.clearRemoteDirectoryFiles(req.getMachineHost(), Integer.parseInt(req.getSshPort()), req.getUsername(), req.getPassword(), null, remoteFilePath + "PIP_" + req.getPipelineId() + crashesFilePath); + System.out.println("crashes目录下文件已删除: " + remoteFilePath + "PIP_" + req.getPipelineId() + crashesFilePath); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflFileChunkServiceImpl.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflFileChunkServiceImpl.java new file mode 100644 index 00000000..7dd9193c --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflFileChunkServiceImpl.java @@ -0,0 +1,104 @@ +package cd.casic.ci.process.process.service.aflManager.impl; + +import cd.casic.ci.process.process.dao.aflManager.AflFileChunkDao; +import cd.casic.ci.process.process.dataObject.aflManager.AflFileChunk; +import cd.casic.ci.process.process.service.aflManager.AflFileChunkService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import de.danielbechler.util.Collections; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.UUID; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName TargetVersionServiceImpl + * @Date: 2025/5/17 15:20 + * @Description: + */ +@Service +@Slf4j +public class AflFileChunkServiceImpl extends ServiceImpl implements AflFileChunkService { + @Resource + private AflFileChunkDao aflFileChunkDao; + + // 每个分片大小为 2GB + private static final Long CHUNK_SIZE = 1024 * 1024 * 1024 * 2L; + + @Override + public byte[] getFileById(String contentId) throws IOException { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("content_id", contentId); + List chunks = aflFileChunkDao.selectList(wrapper); + if (chunks == null || chunks.isEmpty()) { + return new byte[0]; + } + + int totalLength = chunks.stream() + .mapToInt(chunk -> chunk.getChunkContent() != null ? chunk.getChunkContent().length : 0) + .sum(); + + byte[] result = new byte[totalLength]; + int position = 0; + + for (AflFileChunk chunk : chunks) { + byte[] data = chunk.getChunkContent(); + if (data != null) { + System.arraycopy(data, 0, result, position, data.length); + position += data.length; + } + } + return result; + } + + @Override + public String saveFileChunk(InputStream file) { + if (file == null) { + return null; + } + + String contentId = UUID.randomUUID().toString(); + + try { + byte[] buffer = new byte[CHUNK_SIZE.intValue()]; + int bytesRead; + int chunkIndex = 0; + + // 第一次遍历:读取所有分片并保存 + while ((bytesRead = file.read(buffer)) != -1) { + byte[] chunkData = new byte[bytesRead]; // 实际读取的大小 + System.arraycopy(buffer, 0, chunkData, 0, bytesRead); + + AflFileChunk chunk = new AflFileChunk(); + chunk.setContentId(contentId); + chunk.setChunkIndex(chunkIndex++); + chunk.setChunkContent(chunkData); + + this.baseMapper.insert(chunk); + } + + // 第二次遍历:更新每个分片的总分片数 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("content_id", contentId); + List aflFileChunks = baseMapper.selectList(wrapper); + if (!Collections.isEmpty(aflFileChunks)){ + aflFileChunks.forEach(o->{ + o.setChunksSum(aflFileChunks.size()); + }); + } + baseMapper.updateBatch(aflFileChunks); + + } catch (IOException e) { + log.error("文件分片上传失败", e); + throw new RuntimeException("文件上传失败"); + } + + return contentId; + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflInfoServiceImpl.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflInfoServiceImpl.java new file mode 100644 index 00000000..1aa24fff --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflInfoServiceImpl.java @@ -0,0 +1,76 @@ +package cd.casic.ci.process.process.service.aflManager.impl; + +import cd.casic.ci.process.dto.req.aflManager.AflManagerReq; +import cd.casic.ci.process.process.dao.aflManager.AflInfoDao; +import cd.casic.ci.process.process.dataObject.aflManager.AflInfo; +import cd.casic.ci.process.process.dataObject.resource.PipResourceMachine; +import cd.casic.ci.process.process.service.aflManager.AflInfoService; +import cd.casic.ci.process.util.SftpUploadUtil; +import cd.casic.framework.commons.exception.ServiceException; +import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName TargetVersionServiceImpl + * @Date: 2025/5/17 15:20 + * @Description: + */ +@Service +@Slf4j +public class AflInfoServiceImpl extends ServiceImpl implements AflInfoService { + private static final String remoteFilePath = "/home/casic/706/yunqi/"; + + + @Override + public String saveAflInfo(AflManagerReq req) throws SftpUploadUtil.SftpUploadException { + if (StringUtils.isEmpty(req.getPipelineId())){ + throw new ServiceException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),"pipelineId不能为空"); + } + PipResourceMachine pipResourceMachine = new PipResourceMachine(); + pipResourceMachine.setMachineHost(req.getMachineHost()); + pipResourceMachine.setSshPort(req.getSshPort()); + pipResourceMachine.setUsername(req.getUsername()); + pipResourceMachine.setPassword(req.getPassword()); + AflInfo aflInfo = SftpUploadUtil.downloadFileSftpForInputStreamAndSetAflInfo(req.getMachineHost(), Integer.parseInt(req.getSshPort()), req.getUsername(), req.getPassword(),null, remoteFilePath + "PIP_" + req.getPipelineId() + "/ai_afl/default/fuzzer_stats"); + //创建一个新文件夹,将远程文件复制一份过去 + List sourceFilePaths = new ArrayList<>(); + sourceFilePaths.add(remoteFilePath + "PIP_" + req.getPipelineId() + "/ai_afl/default/fuzzer_stats"); + List strings = SftpUploadUtil.copyRemoteFile(pipResourceMachine.getMachineHost(), Integer.parseInt(pipResourceMachine.getSshPort()), pipResourceMachine.getUsername(), pipResourceMachine.getPassword(), null, sourceFilePaths, remoteFilePath + "PIP_" + req.getPipelineId() + "/fuzzer/" + UUID.randomUUID() + "/"); + + aflInfo.setFilePath(strings.get(0)); + aflInfo.setPipelineId(req.getPipelineId()); + aflInfo.setTaskId(req.getTaskId()); + aflInfo.setPipelineHistoryId(req.getPipelineHistoryId()); + baseMapper.insert(aflInfo); + + return aflInfo.getId(); + } + + + + /** + * 获取远程文件的输入流并设值给aflInfo + * + * @param machineInfo 机器信息 + * @param remoteFilePath 远程文件路径 + * @return 文件输入流 + */ + private AflInfo getFileStreamAndSetAflInfo(PipResourceMachine machineInfo, String remoteFilePath) { + + try { + return SftpUploadUtil.downloadFileSftpForInputStreamAndSetAflInfo(machineInfo.getMachineHost(), Integer.parseInt(machineInfo.getSshPort()), machineInfo.getUsername(), machineInfo.getPassword(),null, remoteFilePath); + } catch (Exception e) { + log.error("获取远程文件流失败: {}", e.getMessage()); + return null; + } + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflSeedInfoServiceImpl.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflSeedInfoServiceImpl.java new file mode 100644 index 00000000..4942eabe --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/aflManager/impl/AflSeedInfoServiceImpl.java @@ -0,0 +1,189 @@ +package cd.casic.ci.process.process.service.aflManager.impl; + +import cd.casic.ci.process.dto.req.aflManager.AflManagerReq; +import cd.casic.ci.process.dto.resp.aflManager.SeedsCountResp; +import cd.casic.ci.process.engine.manager.RunContextManager; +import cd.casic.ci.process.engine.runContext.BaseRunContext; +import cd.casic.ci.process.process.dao.aflManager.AflSeedInfoDao; +import cd.casic.ci.process.process.dataObject.aflManager.AflSeedInfo; +import cd.casic.ci.process.process.service.aflManager.AflSeedInfoService; +import cd.casic.ci.process.util.SftpUploadUtil; +import cd.casic.framework.commons.exception.ServiceException; +import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName TargetVersionServiceImpl + * @Date: 2025/5/17 15:20 + * @Description: + */ +@Service +@Slf4j +public class AflSeedInfoServiceImpl extends ServiceImpl implements AflSeedInfoService { + + @Resource + private RunContextManager runContextManager; + + private static final String remoteFilePath = "/home/casic/706/yunqi/"; + + private static final String seedFilePath = "/ai_afl/default/queue/"; + + + @Override + public List findSeedsCount(AflManagerReq req) throws SftpUploadUtil.SftpUploadException { + List seedsCounts = new ArrayList<>(0); + + if (StringUtils.isEmpty(req.getPipelineId())){ + throw new ServiceException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),"pipelineId不能为空"); + } + List resultList = SftpUploadUtil.findSeedCount(req.getMachineHost(), Integer.parseInt(req.getSshPort()), req.getUsername(), req.getPassword(), null, remoteFilePath + "PIP_" + req.getPipelineId() + "/ai_afl/default/queue/"); + + //解析resultList + + //todo 分区计算交由前端处理了,代码暂且注释掉,若前端无法处理则延续算法后端解决 + //分区计算 + //int timeWindow = 0; + for (String o : resultList) { + SeedsCountResp seedsCount = new SeedsCountResp(); + + String[] lineParts = o.split("\\s+创建时间:\\s+"); + if (lineParts.length < 2) { + System.err.println("无效格式: " + o); + break; + } + + String fileNamePart = lineParts[0]; // 文件信息部分 + String createTime = lineParts[1]; // 创建时间 + + // 提取 rep 值 + int repValue = 0; + String seedId = ""; + for (String part : fileNamePart.split(",")) { + if (part.startsWith("rep:")) { + repValue = Integer.parseInt(part.substring(4)); + break; + } + if (part.startsWith("id:")) { + seedId = part.substring(4); + break; + } + } + + // 解析创建时间 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.SSSSSSSSS] XXX"); + ZonedDateTime zonedDateTime = ZonedDateTime.parse(createTime, formatter); + LocalDateTime createDateTime = zonedDateTime.toLocalDateTime(); + + // 获取开始时间 + BaseRunContext runContext = runContextManager.getContext(req.getTaskId()); + LocalDateTime startTime = runContext.getStartTime(); + + // 计算时间差(分钟) + Duration duration = Duration.between(startTime, createDateTime); + int minutesDifference = Math.toIntExact(Math.abs(duration.toMinutes())); + + seedsCount.setRepValue(repValue); + seedsCount.setMinutesDifference(minutesDifference); + seedsCount.setSeedId(seedId); + seedsCounts.add(seedsCount); + +// if (minutesDifference < timeWindow){ +// if (!ObjectUtils.isEmpty(seedsCounts)){ +// //将数据统计到上一个时间窗口中 +// SeedsCountResp seedsCountResp = seedsCounts.get(seedsCounts.size() - 1); +// Integer value = seedsCountResp.getRepValue(); +// seedsCountResp.setRepValue(value + repValue); +// } +// } else if (minutesDifference > timeWindow){ +// timeWindow = timeWindow + 100; +// seedsCount.setTimeWindow(timeWindow); +// seedsCount.setRepValue(repValue); +// seedsCounts.add(seedsCount); +// +// }else { +// if (!ObjectUtils.isEmpty(seedsCounts)){ +// //将数据统计到上一个时间窗口中 +// SeedsCountResp seedsCountResp = seedsCounts.get(seedsCounts.size() - 1); +// Integer value = seedsCountResp.getRepValue(); +// seedsCountResp.setRepValue(value + repValue); +// +// //同时新增下一个100分钟统计的时间窗口数据 +// seedsCount.setTimeWindow(timeWindow + 100); +// seedsCount.setRepValue(0); +// seedsCounts.add(seedsCount); +// } +// } + } + return seedsCounts; + } + + @Override + public void saveAflSeedInfo(AflManagerReq req) { + try { + // 步骤1:列出源目录下的所有文件 + List files = SftpUploadUtil.listFilesInRemoteDirectory( + req.getMachineHost(), Integer.parseInt(req.getSshPort()), req.getUsername(), req.getPassword(), null, remoteFilePath + "PIP_" + req.getPipelineId() + seedFilePath); + + if (!CollectionUtils.isEmpty(files)) { + // 步骤2:批量复制文件到目标目录 + List copiedFiles = SftpUploadUtil.copyRemoteFile( + req.getMachineHost(), Integer.parseInt(req.getSshPort()), req.getUsername(), req.getPassword(), null, files, remoteFilePath + "PIP_" + req.getPipelineId() + "/seed_result/"); + System.out.println("共复制 " + copiedFiles.size() + " 个文件"); + + if (!CollectionUtils.isEmpty(copiedFiles)){ + List aflSeedInfos = new ArrayList<>(0); + //新增数据到crashes表中 + copiedFiles.forEach(o->{ + AflSeedInfo aflSeedInfo = new AflSeedInfo(); + + // 提取文件名部分(queue/之后的内容) + String fileNamePart = o.substring(o.indexOf("seed_result/") + 12); + + // 使用正则匹配 id: 开头的内容 + java.util.regex.Pattern patternId = java.util.regex.Pattern.compile("id:([^,]+)"); + java.util.regex.Matcher matcherId = patternId.matcher(fileNamePart); + + // 使用正则匹配 rep: 开头的内容 + java.util.regex.Pattern patternRep = java.util.regex.Pattern.compile("rep:(\\d+)"); + java.util.regex.Matcher matcherRep = patternRep.matcher(fileNamePart); + + if (matcherRep.find()) { + String repValue = matcherRep.group(1); + aflSeedInfo.setSeedAmount(repValue); + } + + if (matcherId.find()) { + String idValue = matcherId.group(1); + aflSeedInfo.setSeedId(idValue); + } + + aflSeedInfo.setFilePath(o); + aflSeedInfo.setTaskId(req.getTaskId()); + aflSeedInfo.setPipelineId(req.getPipelineId()); + aflSeedInfo.setPipelineHistoryId(req.getPipelineHistoryId()); + + aflSeedInfos.add(aflSeedInfo); + }); + baseMapper.insertBatch(aflSeedInfos); + } + + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/util/SftpUploadUtil.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/util/SftpUploadUtil.java index 22bc0d62..eea6a403 100644 --- a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/util/SftpUploadUtil.java +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/util/SftpUploadUtil.java @@ -1,5 +1,6 @@ package cd.casic.ci.process.util; +import cd.casic.ci.process.process.dataObject.aflManager.AflInfo; import cd.casic.framework.commons.exception.ServiceException; import com.amazonaws.util.IOUtils; import com.jcraft.jsch.*; @@ -8,8 +9,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.multipart.MultipartFile; import java.io.*; -import java.util.List; -import java.util.Vector; +import java.util.*; public class SftpUploadUtil { @@ -506,6 +506,694 @@ public class SftpUploadUtil { } + /** + * 从远程服务器下载文件并返回文件输入流,并设置AflInfo对象 + * + * @param remoteHost 远程服务器地址 + * @param remotePort 远程服务器端口 + * @param username 远程服务器用户名 + * @param password 远程服务器密码 + * @param sshKeyPath SSH密钥路径 + * @param remoteFilePath 远程文件路径 + * @return AflInfo对象,包含文件输入流和文件信息 + * @throws SftpUploadException 如果下载文件或设置AflInfo对象时发生错误 + */ + public static AflInfo downloadFileSftpForInputStreamAndSetAflInfo(String remoteHost, + Integer remotePort, + String username, + String password, + String sshKeyPath, + String remoteFilePath) throws SftpUploadException { + + Session session = null; + Channel channel = null; + ChannelSftp channelSftp = null; + InputStream inputStream = null; + + try { + JSch jsch = new JSch(); + + // 1. 添加身份认证信息 (密码或密钥) + if (sshKeyPath != null && !sshKeyPath.trim().isEmpty()) { + // 使用 SSH Key 认证 + File sshKeyFile = new File(sshKeyPath); + if (!sshKeyFile.exists() || !sshKeyFile.isFile()) { + throw new SftpUploadException("SSH Key 文件不存在或不是一个有效文件: " + sshKeyPath); + } + jsch.addIdentity(sshKeyPath); + System.out.println("使用 SSH Key 认证: " + sshKeyPath); + } else if (password == null || password.trim().isEmpty()) { + // 如果没有提供密码或密钥路径,则认证信息不全 + throw new SftpUploadException("必须提供密码或 SSH Key 路径进行 SFTP 认证."); + } + // 如果提供了密码,将在 getSession 后设置,因为 getSession 需要用户名、主机和端口先建立连接意图 + + + // 2. 获取 Session + int port = (remotePort != null && remotePort > 0) ? remotePort : DEFAULT_SFTP_PORT; + session = jsch.getSession(username, remoteHost, port); + System.out.println("尝试连接 SFTP 服务器: " + username + "@" + remoteHost + ":" + port); + + + // 如果使用密码认证且提供了密码 + if (password != null && !password.trim().isEmpty() && (sshKeyPath == null || sshKeyPath.trim().isEmpty())) { + session.setPassword(password); + System.out.println("使用密码认证."); + } + + // 设置连接不进行主机密钥检查 (生产环境不推荐,应该配置 known_hosts) + // 在实际应用中,应该引导用户信任主机密钥或提前将主机密钥加入 known_hosts + java.util.Properties config = new java.util.Properties(); + config.put("StrictHostKeyChecking", "no"); // !!! 生产环境请谨慎使用或配置正确的主机密钥检查 !!! + session.setConfig(config); + + // 3. 连接 Session + session.connect(); + System.out.println("SFTP Session 连接成功."); + + // 4. 打开 SFTP Channel + channel = session.openChannel("sftp"); + channel.connect(); + System.out.println("SFTP Channel 打开成功."); + + channelSftp = (ChannelSftp) channel; + + // 获取远程文件名和目录 + String remoteDir = remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/')); + String fileName = remoteFilePath.substring(remoteFilePath.lastIndexOf('/') + 1); + + // 切换目录并列出内容用于调试 + try { + channelSftp.cd(remoteDir); + } catch (SftpException e) { + throw new SftpUploadException("切换远程目录失败: " + remoteDir, e); + } + + // 列出目录内容用于调试 + Vector entries = channelSftp.ls("."); + List fileNames = entries.stream() + .map(entry -> entry.getFilename()) + .filter(name -> !name.equals(".") && !name.equals("..")) + .toList(); + + System.out.println("远程目录中的文件列表: " + fileNames); + + // 尝试获取文件流 + try { + inputStream = channelSftp.get(fileName); + } catch (SftpException e) { + throw new SftpUploadException("获取远程文件流失败: " + fileName, e); + } + + if (inputStream == null) { + throw new SftpUploadException("无法获取远程文件输入流,请检查文件是否存在或被其他进程占用: " + fileName); + } + + //转字符串 + String fileContent = null; + try { + fileContent = IOUtils.toString(inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + + //解析 key=value 内容 + Map statsMap = new HashMap<>(); + for (String line : fileContent.split("\\r?\\n")) { + if (line.contains("=")) { + String[] parts = line.split("=", 2); + statsMap.put(parts[0].trim(), parts[1].trim()); + } + } + + return setAflinfo(statsMap); + } catch (JSchException e) { + throw new SftpUploadException("SFTP 连接或认证失败: " + e.getMessage(), e); + } catch (SftpException e) { + throw new SftpUploadException("SFTP 操作失败 (如切换目录或上传文件): " + e.getMessage(), e); + } catch (SftpUploadException e) { + // 重新抛出自定义异常 + throw e; + } catch (Exception e) { + // 捕获其他未知异常 + throw new SftpUploadException("SFTP 上传过程中发生未知异常: " + e.getMessage(), e); + } finally { + // 9. 关闭资源 (确保在任何情况下都关闭) + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + System.err.println("关闭本地文件流失败: " + e.getMessage()); + e.printStackTrace(); // 打印堆栈以便调试 + } + } + if (channelSftp != null) { + channelSftp.disconnect(); + System.out.println("SFTP Channel 已断开."); + } + if (channel != null) { + channel.disconnect(); + System.out.println("SFTP Channel 资源已释放."); + } + if (session != null) { + session.disconnect(); + System.out.println("SFTP Session 已断开."); + } + } + + } + + + + public static List copyRemoteFile(String remoteHost, + Integer remotePort, + String username, + String password, + String sshKeyPath, + List sourceFilePaths, + String targetDir) throws SftpUploadException { + + Session session = null; + Channel channel = null; + ChannelSftp channelSftp = null; + InputStream inputStream = null; + + try { + JSch jsch = new JSch(); + + // 1. 添加身份认证信息 (密码或密钥) + if (sshKeyPath != null && !sshKeyPath.trim().isEmpty()) { + // 使用 SSH Key 认证 + File sshKeyFile = new File(sshKeyPath); + if (!sshKeyFile.exists() || !sshKeyFile.isFile()) { + throw new SftpUploadException("SSH Key 文件不存在或不是一个有效文件: " + sshKeyPath); + } + jsch.addIdentity(sshKeyPath); + System.out.println("使用 SSH Key 认证: " + sshKeyPath); + } else if (password == null || password.trim().isEmpty()) { + // 如果没有提供密码或密钥路径,则认证信息不全 + throw new SftpUploadException("必须提供密码或 SSH Key 路径进行 SFTP 认证."); + } + // 如果提供了密码,将在 getSession 后设置,因为 getSession 需要用户名、主机和端口先建立连接意图 + + + // 2. 获取 Session + int port = (remotePort != null && remotePort > 0) ? remotePort : DEFAULT_SFTP_PORT; + session = jsch.getSession(username, remoteHost, port); + System.out.println("尝试连接 SFTP 服务器: " + username + "@" + remoteHost + ":" + port); + + + // 如果使用密码认证且提供了密码 + if (password != null && !password.trim().isEmpty() && (sshKeyPath == null || sshKeyPath.trim().isEmpty())) { + session.setPassword(password); + System.out.println("使用密码认证."); + } + + // 设置连接不进行主机密钥检查 (生产环境不推荐,应该配置 known_hosts) + // 在实际应用中,应该引导用户信任主机密钥或提前将主机密钥加入 known_hosts + java.util.Properties config = new java.util.Properties(); + config.put("StrictHostKeyChecking", "no"); // !!! 生产环境请谨慎使用或配置正确的主机密钥检查 !!! + session.setConfig(config); + + // 3. 连接 Session + session.connect(); + System.out.println("SFTP Session 连接成功."); + + // 4. 打开 SFTP Channel + channel = session.openChannel("sftp"); + channel.connect(); + System.out.println("SFTP Channel 打开成功."); + + channelSftp = (ChannelSftp) channel; + + // 创建目标目录(递归) + createRemoteDirRecursive(channelSftp, targetDir); + + // 切换到目标目录 + channelSftp.cd(targetDir); + + List copiedFiles = new ArrayList<>(); + + for (String sourceFilePath : sourceFilePaths) { + // 获取源文件名 + String sourceFileName = sourceFilePath.substring(sourceFilePath.lastIndexOf('/') + 1); + + // 切换到源目录 + String sourceDir = sourceFilePath.substring(0, sourceFilePath.lastIndexOf('/')); + channelSftp.cd(sourceDir); + + // 获取文件输入流 + inputStream = channelSftp.get(sourceFileName); + + // 复制文件 + channelSftp.put(inputStream, sourceFileName); + + System.out.println("文件复制成功: " + sourceFilePath + " -> " + targetDir + "/" + sourceFileName); + copiedFiles.add(targetDir + "/" + sourceFileName); + + // 关闭输入流以释放资源 + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + System.err.println("关闭本地文件流失败: " + e.getMessage()); + e.printStackTrace(); // 打印堆栈以便调试 + } + inputStream = null; // 重置 inputStream 以便下一个文件使用 + } + } + + return copiedFiles; + } catch (JSchException e) { + throw new SftpUploadException("SFTP 连接或认证失败: " + e.getMessage(), e); + } catch (SftpException e) { + throw new SftpUploadException("SFTP 操作失败 (如切换目录或上传文件): " + e.getMessage(), e); + } catch (SftpUploadException e) { + // 重新抛出自定义异常 + throw e; + } catch (Exception e) { + // 捕获其他未知异常 + throw new SftpUploadException("SFTP 上传过程中发生未知异常: " + e.getMessage(), e); + } finally { + // 9. 关闭资源 (确保在任何情况下都关闭) + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + System.err.println("关闭本地文件流失败: " + e.getMessage()); + e.printStackTrace(); // 打印堆栈以便调试 + } + } + if (channelSftp != null) { + channelSftp.disconnect(); + System.out.println("SFTP Channel 已断开."); + } + if (channel != null) { + channel.disconnect(); + System.out.println("SFTP Channel 资源已释放."); + } + if (session != null) { + session.disconnect(); + System.out.println("SFTP Session 已断开."); + } + } + } + + + public static List listFilesInRemoteDirectory( String remoteHost, + Integer remotePort, + String username, + String password, + String sshKeyPath, + String remoteDir) throws SftpUploadException { + + Session session = null; + Channel channel = null; + ChannelSftp channelSftp = null; + + try { + JSch jsch = new JSch(); + + // 1. 添加身份认证信息 (密码或密钥) + if (sshKeyPath != null && !sshKeyPath.trim().isEmpty()) { + // 使用 SSH Key 认证 + File sshKeyFile = new File(sshKeyPath); + if (!sshKeyFile.exists() || !sshKeyFile.isFile()) { + throw new SftpUploadException("SSH Key 文件不存在或不是一个有效文件: " + sshKeyPath); + } + jsch.addIdentity(sshKeyPath); + System.out.println("使用 SSH Key 认证: " + sshKeyPath); + } else if (password == null || password.trim().isEmpty()) { + // 如果没有提供密码或密钥路径,则认证信息不全 + throw new SftpUploadException("必须提供密码或 SSH Key 路径进行 SFTP 认证."); + } + // 如果提供了密码,将在 getSession 后设置,因为 getSession 需要用户名、主机和端口先建立连接意图 + + + // 2. 获取 Session + int port = (remotePort != null && remotePort > 0) ? remotePort : DEFAULT_SFTP_PORT; + session = jsch.getSession(username, remoteHost, port); + System.out.println("尝试连接 SFTP 服务器: " + username + "@" + remoteHost + ":" + port); + + + // 如果使用密码认证且提供了密码 + if (password != null && !password.trim().isEmpty() && (sshKeyPath == null || sshKeyPath.trim().isEmpty())) { + session.setPassword(password); + System.out.println("使用密码认证."); + } + + // 设置连接不进行主机密钥检查 (生产环境不推荐,应该配置 known_hosts) + // 在实际应用中,应该引导用户信任主机密钥或提前将主机密钥加入 known_hosts + java.util.Properties config = new java.util.Properties(); + config.put("StrictHostKeyChecking", "no"); // !!! 生产环境请谨慎使用或配置正确的主机密钥检查 !!! + session.setConfig(config); + + // 3. 连接 Session + session.connect(); + System.out.println("SFTP Session 连接成功."); + + // 4. 打开 SFTP Channel + channel = session.openChannel("sftp"); + channel.connect(); + System.out.println("SFTP Channel 打开成功."); + + channelSftp = (ChannelSftp) channel; + + // 切换到目标目录 + channelSftp.cd(remoteDir); + + // 列出文件 + Vector entries = channelSftp.ls("."); + List fileNames = new ArrayList<>(); + for (ChannelSftp.LsEntry entry : entries) { + String name = entry.getFilename(); + if (!name.equals(".") && !name.equals("..")) { + fileNames.add(remoteDir + name); // 返回完整路径 + } + } + + return fileNames; + } catch (JSchException e) { + throw new SftpUploadException("SFTP 连接或认证失败: " + e.getMessage(), e); + } catch (SftpException e) { + throw new SftpUploadException("SFTP 操作失败 (如切换目录或上传文件): " + e.getMessage(), e); + } catch (SftpUploadException e) { + // 重新抛出自定义异常 + throw e; + } catch (Exception e) { + // 捕获其他未知异常 + throw new SftpUploadException("SFTP 上传过程中发生未知异常: " + e.getMessage(), e); + } finally { + if (channelSftp != null) { + channelSftp.disconnect(); + System.out.println("SFTP Channel 已断开."); + } + if (channel != null) { + channel.disconnect(); + System.out.println("SFTP Channel 资源已释放."); + } + if (session != null) { + session.disconnect(); + System.out.println("SFTP Session 已断开."); + } + } + } + + public static void clearRemoteDirectoryFiles( String remoteHost, + Integer remotePort, + String username, + String password, + String sshKeyPath, + String remoteDir) throws SftpUploadException { + + Session session = null; + Channel channel = null; + ChannelSftp channelSftp = null; + + try { + JSch jsch = new JSch(); + + // 1. 添加身份认证信息 (密码或密钥) + if (sshKeyPath != null && !sshKeyPath.trim().isEmpty()) { + // 使用 SSH Key 认证 + File sshKeyFile = new File(sshKeyPath); + if (!sshKeyFile.exists() || !sshKeyFile.isFile()) { + throw new SftpUploadException("SSH Key 文件不存在或不是一个有效文件: " + sshKeyPath); + } + jsch.addIdentity(sshKeyPath); + System.out.println("使用 SSH Key 认证: " + sshKeyPath); + } else if (password == null || password.trim().isEmpty()) { + // 如果没有提供密码或密钥路径,则认证信息不全 + throw new SftpUploadException("必须提供密码或 SSH Key 路径进行 SFTP 认证."); + } + // 如果提供了密码,将在 getSession 后设置,因为 getSession 需要用户名、主机和端口先建立连接意图 + + + // 2. 获取 Session + int port = (remotePort != null && remotePort > 0) ? remotePort : DEFAULT_SFTP_PORT; + session = jsch.getSession(username, remoteHost, port); + System.out.println("尝试连接 SFTP 服务器: " + username + "@" + remoteHost + ":" + port); + + + // 如果使用密码认证且提供了密码 + if (password != null && !password.trim().isEmpty() && (sshKeyPath == null || sshKeyPath.trim().isEmpty())) { + session.setPassword(password); + System.out.println("使用密码认证."); + } + + // 设置连接不进行主机密钥检查 (生产环境不推荐,应该配置 known_hosts) + // 在实际应用中,应该引导用户信任主机密钥或提前将主机密钥加入 known_hosts + java.util.Properties config = new java.util.Properties(); + config.put("StrictHostKeyChecking", "no"); // !!! 生产环境请谨慎使用或配置正确的主机密钥检查 !!! + session.setConfig(config); + + // 3. 连接 Session + session.connect(); + System.out.println("SFTP Session 连接成功."); + + // 4. 打开 SFTP Channel + channel = session.openChannel("sftp"); + channel.connect(); + System.out.println("SFTP Channel 打开成功."); + + channelSftp = (ChannelSftp) channel; + + // 切换到目标目录 + channelSftp.cd(remoteDir); + + // 列出目录下的所有条目 + Vector lsEntriesVector = channelSftp.ls("."); + + // 转换为 List 并过滤文件(排除 . 和 ..) + List entries = new ArrayList<>(); + for (ChannelSftp.LsEntry entry : lsEntriesVector) { + String name = entry.getFilename(); + if (!name.equals(".") && !name.equals("..")) { + entries.add(entry); + } + } + + // 遍历并删除所有文件(不包括子目录) + for (ChannelSftp.LsEntry entry : entries) { + String name = entry.getFilename(); + String fullPath = remoteDir + "/" + name; + + // 检查是否是文件(非目录) + if (!thisDirectory(channelSftp, fullPath)) { + channelSftp.rm(fullPath); // 删除文件 + System.out.println("已删除文件: " + fullPath); + } + } + } catch (JSchException e) { + throw new SftpUploadException("SFTP 连接或认证失败: " + e.getMessage(), e); + } catch (SftpException e) { + throw new SftpUploadException("SFTP 操作失败 (如切换目录或上传文件): " + e.getMessage(), e); + } catch (SftpUploadException e) { + // 重新抛出自定义异常 + throw e; + } catch (Exception e) { + // 捕获其他未知异常 + throw new SftpUploadException("SFTP 上传过程中发生未知异常: " + e.getMessage(), e); + } finally { + if (channelSftp != null) { + channelSftp.disconnect(); + System.out.println("SFTP Channel 已断开."); + } + if (channel != null) { + channel.disconnect(); + System.out.println("SFTP Channel 资源已释放."); + } + if (session != null) { + session.disconnect(); + System.out.println("SFTP Session 已断开."); + } + } + } + + + + private static AflInfo setAflinfo(Map statsMap) { + //映射到 AflInfo 实体 + AflInfo aflInfo = new AflInfo(); + + aflInfo.setStartTime(statsMap.get("start_time")); + aflInfo.setLastUpdate(statsMap.get("last_update")); + aflInfo.setRunTime(statsMap.get("run_time")); + aflInfo.setFuzzerPid(statsMap.get("fuzzer_pid")); + aflInfo.setCyclesDone(statsMap.get("cycles_done")); + aflInfo.setCyclesWoFinds(statsMap.get("cycles_wo_finds")); + aflInfo.setTimeWoFinds(statsMap.get("time_wo_finds")); + aflInfo.setFuzzTime(statsMap.get("fuzz_time")); + aflInfo.setCalibrationTime(statsMap.get("calibration_time")); + aflInfo.setCmplogTime(statsMap.get("cmplog_time")); + aflInfo.setSyncTime(statsMap.get("sync_time")); + aflInfo.setTrimTime(statsMap.get("trim_time")); + aflInfo.setExecsDone(statsMap.get("execs_done")); + aflInfo.setExecsPerSec(statsMap.get("execs_per_sec")); + aflInfo.setExecsPsLastMin(statsMap.get("execs_ps_last_min")); + aflInfo.setCorpusCount(statsMap.get("corpus_count")); + aflInfo.setCorpusFavored(statsMap.get("corpus_favored")); + aflInfo.setCorpusFound(statsMap.get("corpus_found")); + aflInfo.setCorpusImported(statsMap.get("corpus_imported")); + aflInfo.setCorpusVariable(statsMap.get("corpus_variable")); + aflInfo.setMaxDepth(statsMap.get("max_depth")); + aflInfo.setCurItem(statsMap.get("cur_item")); + aflInfo.setPendingFavs(statsMap.get("pending_favs")); + aflInfo.setPendingTotal(statsMap.get("pending_total")); + aflInfo.setStability(statsMap.get("stability")); + aflInfo.setBitmapCvg(statsMap.get("bitmap_cvg")); + aflInfo.setSavedCrashes(statsMap.get("saved_crashes")); + aflInfo.setSavedHangs(statsMap.get("saved_hangs")); + aflInfo.setTotalTmout(statsMap.get("total_tmout")); + aflInfo.setLastFind(statsMap.get("last_find")); + aflInfo.setLastCrash(statsMap.get("last_crash")); + aflInfo.setLastHang(statsMap.get("last_hang")); + aflInfo.setExecsSinceCrash(statsMap.get("execs_since_crash")); + aflInfo.setExecTimeout(statsMap.get("exec_timeout")); + aflInfo.setSlowestExecMs(statsMap.get("slowest_exec_ms")); + aflInfo.setPeakRssMb(statsMap.get("peak_rss_mb")); + aflInfo.setCpuAffinity(statsMap.get("cpu_affinity")); + aflInfo.setEdgesFound(statsMap.get("edges_found")); + aflInfo.setTotalEdges(statsMap.get("total_edges")); + aflInfo.setVarByteCount(statsMap.get("var_byte_count")); + aflInfo.setHavocExpansion(statsMap.get("havoc_expansion")); + aflInfo.setAutoDictEntries(statsMap.get("auto_dict_entries")); + aflInfo.setTestcacheSize(statsMap.get("testcache_size")); + aflInfo.setTestcacheCount(statsMap.get("testcache_count")); + aflInfo.setTestcacheEvict(statsMap.get("testcache_evict")); + aflInfo.setAflBanner(statsMap.get("afl_banner")); + aflInfo.setAflVersion(statsMap.get("afl_version")); + aflInfo.setTargetMode(statsMap.get("target_mode")); + aflInfo.setCommandLine(statsMap.get("command_line")); + + return aflInfo; + } + + + + private static boolean thisDirectory(ChannelSftp channelSftp, String fullPath) { + try { + SftpATTRS attrs = channelSftp.lstat(fullPath); + return attrs.isDir(); + } catch (SftpException e) { + return false; + } + } + + public static List findSeedCount(String remoteHost, + Integer remotePort, + String username, + String password, + String sshKeyPath, + String remoteDir) throws SftpUploadException { + + Session session = null; + + try { + JSch jsch = new JSch(); + + // 1. 添加身份认证信息 (密码或密钥) + if (sshKeyPath != null && !sshKeyPath.trim().isEmpty()) { + // 使用 SSH Key 认证 + File sshKeyFile = new File(sshKeyPath); + if (!sshKeyFile.exists() || !sshKeyFile.isFile()) { + throw new SftpUploadException("SSH Key 文件不存在或不是一个有效文件: " + sshKeyPath); + } + jsch.addIdentity(sshKeyPath); + System.out.println("使用 SSH Key 认证: " + sshKeyPath); + } else if (password == null || password.trim().isEmpty()) { + // 如果没有提供密码或密钥路径,则认证信息不全 + throw new SftpUploadException("必须提供密码或 SSH Key 路径进行 SFTP 认证."); + } + // 如果提供了密码,将在 getSession 后设置,因为 getSession 需要用户名、主机和端口先建立连接意图 + + + // 2. 获取 Session + int port = (remotePort != null && remotePort > 0) ? remotePort : DEFAULT_SFTP_PORT; + session = jsch.getSession(username, remoteHost, port); + System.out.println("尝试连接 SFTP 服务器: " + username + "@" + remoteHost + ":" + port); + + + // 如果使用密码认证且提供了密码 + if (password != null && !password.trim().isEmpty() && (sshKeyPath == null || sshKeyPath.trim().isEmpty())) { + session.setPassword(password); + System.out.println("使用密码认证."); + } + + // 设置连接不进行主机密钥检查 (生产环境不推荐,应该配置 known_hosts) + // 在实际应用中,应该引导用户信任主机密钥或提前将主机密钥加入 known_hosts + java.util.Properties config = new java.util.Properties(); + config.put("StrictHostKeyChecking", "no"); // !!! 生产环境请谨慎使用或配置正确的主机密钥检查 !!! + session.setConfig(config); + + // 3. 连接 Session + session.connect(); + System.out.println("SFTP Session 连接成功."); + + //切换目录 + ChannelSftp sftpChannel = (ChannelSftp) session.openChannel("sftp"); + sftpChannel.connect(); + try { + sftpChannel.cd(remoteDir); + System.out.println("切换到远程目录: " + remoteDir); + } catch (SftpException e) { + System.err.println("切换目录失败: " + e.getMessage()); + } + sftpChannel.disconnect(); + + // 执行命令打印输出 + ChannelExec execChannel = (ChannelExec) session.openChannel("exec"); + execChannel.setCommand("find . -maxdepth 1 -type f -exec stat -c \"%n 创建时间: %w\" {} \\;"); + InputStream in = execChannel.getInputStream(); + execChannel.connect(); + + byte[] tmp = new byte[1024]; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + while (true) { + while (in.available() > 0) { + int i = in.read(tmp, 0, 1024); + if (i < 0) break; + outputStream.write(tmp, 0, i); + } + + if (execChannel.isClosed()) { + if (in.available() > 0) continue; + break; + } + + try { + Thread.sleep(1000); + } catch (Exception ignored) {} + } + + // 转成字符串并按行拆分 + String outputStr = outputStream.toString(); + String[] lines = outputStr.split("\\r?\\n"); + + // 添加进 list + List result = new ArrayList<>(Arrays.asList(lines)); + execChannel.disconnect(); + + return result; + } catch (JSchException e) { + throw new SftpUploadException("SFTP 连接或认证失败: " + e.getMessage(), e); + } catch (SftpUploadException e) { + // 重新抛出自定义异常 + throw e; + } catch (Exception e) { + // 捕获其他未知异常 + throw new SftpUploadException("SFTP 上传过程中发生未知异常: " + e.getMessage(), e); + } finally { + if (session != null) { + session.disconnect(); + System.out.println("SFTP Session 已断开."); + } + } + + } + + // 自定义异常类,用于封装上传过程中的错误 public static class SftpUploadException extends Exception { public SftpUploadException(String message) {