feat(die): integrate DiE analysis and result parsing
This commit is contained in:
parent
3b57f89943
commit
ae8f5b87a6
14
Dockerfile.die
Normal file
14
Dockerfile.die
Normal file
@ -0,0 +1,14 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ARG DIE_VERSION=3.21
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends wget ca-certificates \
|
||||
&& wget -q https://github.com/horsicq/DIE-engine/releases/download/${DIE_VERSION}/die_${DIE_VERSION}_Ubuntu_24.04_amd64.deb \
|
||||
&& apt-get install -y ./die_${DIE_VERSION}_Ubuntu_24.04_amd64.deb \
|
||||
&& rm die_${DIE_VERSION}_Ubuntu_24.04_amd64.deb \
|
||||
&& apt-get purge -y wget \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENTRYPOINT ["/usr/bin/diec"]
|
||||
13
src/main/java/ai/decompile/common/config/JacksonConfig.java
Normal file
13
src/main/java/ai/decompile/common/config/JacksonConfig.java
Normal file
@ -0,0 +1,13 @@
|
||||
package ai.decompile.common.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
return new ObjectMapper();
|
||||
}
|
||||
}
|
||||
20
src/main/java/ai/decompile/die/model/DieResult.java
Normal file
20
src/main/java/ai/decompile/die/model/DieResult.java
Normal file
@ -0,0 +1,20 @@
|
||||
package ai.decompile.die.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record DieResult(@JsonProperty("detects") List<Detect> detects) {
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record Detect(
|
||||
@JsonProperty("filetype") String filetype, @JsonProperty("values") List<Value> values) {}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record Value(
|
||||
@JsonProperty("info") String info,
|
||||
@JsonProperty("string") String string,
|
||||
@JsonProperty("type") String type,
|
||||
@JsonProperty("name") String name) {}
|
||||
}
|
||||
124
src/main/java/ai/decompile/die/model/DieResultParser.java
Normal file
124
src/main/java/ai/decompile/die/model/DieResultParser.java
Normal file
@ -0,0 +1,124 @@
|
||||
package ai.decompile.die.model;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DieResultParser {
|
||||
private static final Pattern COMPILER_PATTERN =
|
||||
Pattern.compile("Compiler:\\s*(.+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern OS_PATTERN =
|
||||
Pattern.compile("Operation system:\\s*(.+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern ARCH_BITS_PATTERN = Pattern.compile("\\[([^,\\]]+),\\s*(\\d+)-bit");
|
||||
|
||||
private static final Map<String, String> FILETYPE_ARCH_HINTS =
|
||||
Map.ofEntries(
|
||||
Map.entry("NE", "x86, 16-bit"),
|
||||
Map.entry("LE", "x86, 32-bit"),
|
||||
Map.entry("LX", "x86, 32-bit"),
|
||||
Map.entry("PE", "x86, 32-bit"),
|
||||
Map.entry("PE32", "x86, 32-bit"),
|
||||
Map.entry("PE64", "x86-64, 64-bit"),
|
||||
Map.entry("PE32+", "x86-64, 64-bit"),
|
||||
Map.entry("MS-DOS", "x86, 16-bit"));
|
||||
|
||||
public ParsedInfo parse(DieResult result) {
|
||||
if (Objects.isNull(result) || Objects.isNull(result.detects()) || result.detects().isEmpty()) {
|
||||
return new ParsedInfo(null, null, null);
|
||||
}
|
||||
|
||||
String fileType = null;
|
||||
String architecture = null;
|
||||
String compiler = null;
|
||||
|
||||
for (var detect : result.detects()) {
|
||||
if (Objects.isNull(fileType) && StringUtils.isNotBlank(detect.filetype())) {
|
||||
fileType = detect.filetype();
|
||||
}
|
||||
|
||||
if (Objects.nonNull(detect.values())) {
|
||||
for (var value : detect.values()) {
|
||||
var text = firstNonBlank(value.string(), value.info(), value.name());
|
||||
|
||||
if (Objects.isNull(compiler)) {
|
||||
compiler = tryExtractCompiler(value, text);
|
||||
}
|
||||
|
||||
if (Objects.isNull(architecture)) {
|
||||
architecture = tryExtractArchitecture(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Objects.nonNull(fileType) && Objects.nonNull(architecture) && Objects.nonNull(compiler)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Objects.isNull(architecture) && Objects.nonNull(fileType)) {
|
||||
architecture = FILETYPE_ARCH_HINTS.get(fileType);
|
||||
}
|
||||
|
||||
return new ParsedInfo(fileType, architecture, compiler);
|
||||
}
|
||||
|
||||
private String tryExtractCompiler(DieResult.Value value, String text) {
|
||||
if (Objects.isNull(text)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var matched = extractFirstMatch(text, COMPILER_PATTERN);
|
||||
if (Objects.nonNull(matched)) {
|
||||
return matched;
|
||||
}
|
||||
|
||||
if ("compiler".equalsIgnoreCase(value.type()) && StringUtils.isNotBlank(value.name())) {
|
||||
var version = StringUtils.isNotBlank(value.info()) ? "[" + value.info() + "]" : "";
|
||||
return value.name() + version;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String tryExtractArchitecture(String text) {
|
||||
if (Objects.isNull(text)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var osInfo = extractFirstMatch(text, OS_PATTERN);
|
||||
if (Objects.isNull(osInfo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var matcher = ARCH_BITS_PATTERN.matcher(osInfo);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1).trim() + ", " + matcher.group(2) + "-bit";
|
||||
}
|
||||
|
||||
return osInfo.trim();
|
||||
}
|
||||
|
||||
private String extractFirstMatch(String text, Pattern pattern) {
|
||||
var matcher = pattern.matcher(text);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1).trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String firstNonBlank(String... values) {
|
||||
for (var v : values) {
|
||||
if (StringUtils.isNotBlank(v)) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public record ParsedInfo(String fileType, String architecture, String compiler) {}
|
||||
}
|
||||
2
src/main/java/ai/decompile/die/model/package-info.java
Normal file
2
src/main/java/ai/decompile/die/model/package-info.java
Normal file
@ -0,0 +1,2 @@
|
||||
@org.springframework.modulith.NamedInterface("model")
|
||||
package ai.decompile.die.model;
|
||||
4
src/main/java/ai/decompile/die/package-info.java
Normal file
4
src/main/java/ai/decompile/die/package-info.java
Normal file
@ -0,0 +1,4 @@
|
||||
@org.springframework.modulith.ApplicationModule(
|
||||
displayName = "DiE",
|
||||
allowedDependencies = {"docker", "common"})
|
||||
package ai.decompile.die;
|
||||
66
src/main/java/ai/decompile/die/service/DiecService.java
Normal file
66
src/main/java/ai/decompile/die/service/DiecService.java
Normal file
@ -0,0 +1,66 @@
|
||||
package ai.decompile.die.service;
|
||||
|
||||
import ai.decompile.die.model.DieResult;
|
||||
import ai.decompile.docker.service.ContainerService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.dockerjava.api.model.Bind;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Log4j2
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(name = "app.docker.enabled", havingValue = "true")
|
||||
public class DiecService {
|
||||
private final ContainerService containers;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Value("${app.die.image}")
|
||||
private String imageName;
|
||||
|
||||
@Value("${app.storage.upload-dir}")
|
||||
private String uploadDir;
|
||||
|
||||
@Value("${app.die.timeout-seconds}")
|
||||
private long timeoutSeconds;
|
||||
|
||||
public DieResult analyze(String filePath) {
|
||||
var absolutePath = Path.of(uploadDir).resolve(filePath).toAbsolutePath().normalize();
|
||||
var filename = absolutePath.getFileName().toString();
|
||||
var hostPath = absolutePath.getParent().toString();
|
||||
|
||||
var dockerfile = new File("Dockerfile.die");
|
||||
containers.ensureImage(imageName, dockerfile);
|
||||
|
||||
var bind = Bind.parse(hostPath + ":/input:ro");
|
||||
|
||||
var containerId =
|
||||
containers.createContainer(imageName, new String[] {"--json", "/input/" + filename}, bind);
|
||||
|
||||
try {
|
||||
containers.startContainer(containerId);
|
||||
|
||||
var exitCode = containers.waitForExit(containerId, timeoutSeconds);
|
||||
var stdout = containers.captureLogs(containerId, 2);
|
||||
|
||||
if (exitCode != 0) {
|
||||
log.error("DiE container exited with code {}: {}", exitCode, stdout);
|
||||
throw new RuntimeException(
|
||||
"DiE container exited with code " + exitCode + ". Output: " + stdout);
|
||||
}
|
||||
|
||||
return objectMapper.readValue(stdout, DieResult.class);
|
||||
} catch (IOException e) {
|
||||
log.error("DiE analysis failed for file {}: {}", filePath, e.getMessage());
|
||||
throw new RuntimeException("DiE analysis failed: " + e.getMessage(), e);
|
||||
} finally {
|
||||
containers.stopAndRemove(containerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/main/java/ai/decompile/die/service/package-info.java
Normal file
2
src/main/java/ai/decompile/die/service/package-info.java
Normal file
@ -0,0 +1,2 @@
|
||||
@org.springframework.modulith.NamedInterface("service")
|
||||
package ai.decompile.die.service;
|
||||
@ -0,0 +1,22 @@
|
||||
package ai.decompile.docker.config;
|
||||
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
import com.github.dockerjava.core.DockerClientBuilder;
|
||||
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
||||
import java.net.URI;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty(name = "app.docker.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class DockerClientConfig {
|
||||
@Bean
|
||||
public DockerClient dockerClient(@Value("${app.die.docker-host}") String dockerHost) {
|
||||
var httpClient =
|
||||
new ApacheDockerHttpClient.Builder().dockerHost(URI.create(dockerHost)).build();
|
||||
|
||||
return DockerClientBuilder.getInstance().withDockerHttpClient(httpClient).build();
|
||||
}
|
||||
}
|
||||
3
src/main/java/ai/decompile/docker/package-info.java
Normal file
3
src/main/java/ai/decompile/docker/package-info.java
Normal file
@ -0,0 +1,3 @@
|
||||
@org.springframework.modulith.ApplicationModule(
|
||||
type = org.springframework.modulith.ApplicationModule.Type.OPEN)
|
||||
package ai.decompile.docker;
|
||||
155
src/main/java/ai/decompile/docker/service/ContainerService.java
Normal file
155
src/main/java/ai/decompile/docker/service/ContainerService.java
Normal file
@ -0,0 +1,155 @@
|
||||
package ai.decompile.docker.service;
|
||||
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
import com.github.dockerjava.api.async.ResultCallback;
|
||||
import com.github.dockerjava.api.command.BuildImageResultCallback;
|
||||
import com.github.dockerjava.api.command.WaitContainerResultCallback;
|
||||
import com.github.dockerjava.api.exception.NotFoundException;
|
||||
import com.github.dockerjava.api.model.Bind;
|
||||
import com.github.dockerjava.api.model.BuildResponseItem;
|
||||
import com.github.dockerjava.api.model.Frame;
|
||||
import com.github.dockerjava.api.model.HostConfig;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Log4j2
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(name = "app.docker.enabled", havingValue = "true")
|
||||
public class ContainerService {
|
||||
private final DockerClient dockerClient;
|
||||
|
||||
public void ensureImage(String imageTag, File dockerfile) {
|
||||
try {
|
||||
dockerClient.inspectImageCmd(imageTag).exec();
|
||||
log.debug("Image '{}' found locally", imageTag);
|
||||
} catch (NotFoundException e) {
|
||||
log.info("Image '{}' not found, building from {}", imageTag, dockerfile.getName());
|
||||
buildImage(imageTag, dockerfile);
|
||||
}
|
||||
}
|
||||
|
||||
public String createContainer(String image, String[] cmd, Bind... binds) {
|
||||
return dockerClient
|
||||
.createContainerCmd(image)
|
||||
.withCmd(cmd)
|
||||
.withHostConfig(HostConfig.newHostConfig().withBinds(binds))
|
||||
.exec()
|
||||
.getId();
|
||||
}
|
||||
|
||||
public void startContainer(String containerId) {
|
||||
dockerClient.startContainerCmd(containerId).exec();
|
||||
}
|
||||
|
||||
public int waitForExit(String containerId, long timeoutSeconds) {
|
||||
return dockerClient
|
||||
.waitContainerCmd(containerId)
|
||||
.exec(new WaitContainerResultCallback())
|
||||
.awaitStatusCode(timeoutSeconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public String captureLogs(String containerId, long timeoutSeconds) {
|
||||
var collector = new FrameCollector();
|
||||
try (collector) {
|
||||
dockerClient
|
||||
.logContainerCmd(containerId)
|
||||
.withStdOut(true)
|
||||
.withStdErr(true)
|
||||
.withFollowStream(true)
|
||||
.exec(collector);
|
||||
|
||||
collector.awaitCompletion(timeoutSeconds, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to capture container logs for {}: {}", containerId, e.getMessage());
|
||||
}
|
||||
return collector.toString();
|
||||
}
|
||||
|
||||
public void stopAndRemove(String containerId) {
|
||||
try {
|
||||
dockerClient.stopContainerCmd(containerId).withTimeout(10).exec();
|
||||
} catch (Exception e) {
|
||||
log.debug(
|
||||
"Failed to stop container {} (may already be stopped): {}", containerId, e.getMessage());
|
||||
}
|
||||
try {
|
||||
dockerClient.removeContainerCmd(containerId).withForce(true).exec();
|
||||
} catch (Exception e) {
|
||||
log.debug(
|
||||
"Failed to remove container {} (may already be removed): {}",
|
||||
containerId,
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void buildImage(String imageTag, File dockerfile) {
|
||||
var absoluteDockerfile = dockerfile.getAbsoluteFile();
|
||||
if (!absoluteDockerfile.exists()) {
|
||||
throw new RuntimeException("Dockerfile not found at " + absoluteDockerfile.getAbsolutePath());
|
||||
}
|
||||
|
||||
Set<String> tags = new HashSet<>();
|
||||
tags.add(imageTag);
|
||||
|
||||
var callback =
|
||||
dockerClient
|
||||
.buildImageCmd(absoluteDockerfile)
|
||||
.withTags(tags)
|
||||
.exec(
|
||||
new BuildImageResultCallback() {
|
||||
@Override
|
||||
public void onNext(BuildResponseItem item) {
|
||||
if (Objects.nonNull(item.getStream())) {
|
||||
log.debug("Build: {}", item.getStream().trim());
|
||||
}
|
||||
|
||||
if (item.isErrorIndicated() && Objects.nonNull(item.getErrorDetail())) {
|
||||
log.error("Build error: {}", item.getErrorDetail().getMessage());
|
||||
}
|
||||
|
||||
super.onNext(item);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
callback.awaitCompletion();
|
||||
log.info("Image '{}' built successfully", imageTag);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Interrupted while building image " + imageTag, e);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to build image " + imageTag + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FrameCollector extends ResultCallback.Adapter<Frame> implements Closeable {
|
||||
private final StringBuilder buffer = new StringBuilder();
|
||||
|
||||
@Override
|
||||
public void onNext(Frame frame) {
|
||||
buffer.append(new String(frame.getPayload(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,11 @@
|
||||
@org.springframework.modulith.ApplicationModule(
|
||||
displayName = "Job",
|
||||
allowedDependencies = {"workspace::events", "workspace::entities", "common"})
|
||||
allowedDependencies = {
|
||||
"workspace::events",
|
||||
"workspace::entities",
|
||||
"workspace::services",
|
||||
"die::service",
|
||||
"die::model",
|
||||
"common"
|
||||
})
|
||||
package ai.decompile.job;
|
||||
|
||||
@ -1,14 +1,28 @@
|
||||
package ai.decompile.job.service.handler;
|
||||
|
||||
import ai.decompile.die.model.DieResultParser;
|
||||
import ai.decompile.die.service.DiecService;
|
||||
import ai.decompile.job.model.entity.Job;
|
||||
import ai.decompile.job.model.enums.JobStatus;
|
||||
import ai.decompile.job.model.enums.JobType;
|
||||
import ai.decompile.workspace.service.BinaryService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Log4j2
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(name = "app.docker.enabled", havingValue = "true")
|
||||
public class AnalyzeFileHandler implements JobHandler {
|
||||
private final DiecService diecService;
|
||||
private final DieResultParser dieResultParser;
|
||||
private final BinaryService binaryService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public void process(Job job) {
|
||||
log.info(
|
||||
@ -17,13 +31,35 @@ public class AnalyzeFileHandler implements JobHandler {
|
||||
job.getBinaryId(),
|
||||
job.getProjectId());
|
||||
|
||||
var filePath = binaryService.getBinaryFilePath(job.getBinaryId());
|
||||
var result = diecService.analyze(filePath);
|
||||
var parsed = dieResultParser.parse(result);
|
||||
|
||||
binaryService.updateAnalysisInfo(
|
||||
job.getBinaryId(), parsed.fileType(), parsed.architecture(), parsed.compiler());
|
||||
|
||||
job.setResult(toJson(Map.of("raw", result, "info", parsed)));
|
||||
job.setStatus(JobStatus.COMPLETED);
|
||||
|
||||
log.info("Job {} completed", job.getId());
|
||||
log.info(
|
||||
"Job {} completed. fileType={}, arch={}, compiler={}",
|
||||
job.getId(),
|
||||
parsed.fileType(),
|
||||
parsed.architecture(),
|
||||
parsed.compiler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobType supportedType() {
|
||||
return JobType.ANALYZE_FILE;
|
||||
}
|
||||
|
||||
private String toJson(Object obj) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(obj);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to serialize result to JSON", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ public record BinaryResponse(
|
||||
long fileSize,
|
||||
String format,
|
||||
String architecture,
|
||||
String compiler,
|
||||
@NotBlank String filePath,
|
||||
@NotNull Instant updatedAt) {
|
||||
public static BinaryResponse from(Binary binary) {
|
||||
@ -23,6 +24,7 @@ public record BinaryResponse(
|
||||
binary.getFileSize(),
|
||||
binary.getFormat(),
|
||||
binary.getArchitecture(),
|
||||
binary.getCompiler(),
|
||||
binary.getFilePath(),
|
||||
binary.getUpdatedAt());
|
||||
}
|
||||
|
||||
@ -25,12 +25,15 @@ public class Binary {
|
||||
@Column(name = "file_size", nullable = false)
|
||||
private long fileSize;
|
||||
|
||||
@Column(length = 20)
|
||||
@Column(length = 30)
|
||||
private String format;
|
||||
|
||||
@Column(length = 20)
|
||||
@Column(length = 30)
|
||||
private String architecture;
|
||||
|
||||
@Column(length = 100)
|
||||
private String compiler;
|
||||
|
||||
@Column(name = "file_path", nullable = false, length = 500)
|
||||
private String filePath;
|
||||
|
||||
|
||||
@ -67,6 +67,16 @@ public class BinaryService {
|
||||
return BinaryResponse.from(binary);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateAnalysisInfo(
|
||||
UUID binaryId, String format, String architecture, String compiler) {
|
||||
var binary = getBinary(binaryId);
|
||||
binary.setFormat(format);
|
||||
binary.setArchitecture(architecture);
|
||||
binary.setCompiler(compiler);
|
||||
binaryRepository.save(binary);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteBinary(UUID binaryId) {
|
||||
var binary = getBinary(binaryId);
|
||||
@ -90,6 +100,10 @@ public class BinaryService {
|
||||
}
|
||||
}
|
||||
|
||||
public String getBinaryFilePath(UUID binaryId) {
|
||||
return getBinary(binaryId).getFilePath();
|
||||
}
|
||||
|
||||
private Binary getBinary(UUID binaryId) {
|
||||
return binaryRepository
|
||||
.findById(binaryId)
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
@org.springframework.modulith.NamedInterface("services")
|
||||
package ai.decompile.workspace.service;
|
||||
@ -1,4 +1,7 @@
|
||||
spring:
|
||||
threads:
|
||||
virtual:
|
||||
enabled: true
|
||||
application:
|
||||
name: decompile-ai
|
||||
datasource:
|
||||
@ -27,6 +30,12 @@ app:
|
||||
exchange: decompile.jobs
|
||||
queue: decompile.jobs.queue
|
||||
routing-key: decompile.jobs.created
|
||||
docker:
|
||||
enabled: true
|
||||
die:
|
||||
image: decompile-ai/diec:latest
|
||||
timeout-seconds: 60
|
||||
docker-host: unix:///var/run/docker.sock
|
||||
|
||||
logging:
|
||||
level:
|
||||
|
||||
@ -4,8 +4,9 @@ CREATE TABLE binaries
|
||||
project_id UUID NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
|
||||
filename VARCHAR(255) NOT NULL,
|
||||
file_size BIGINT NOT NULL,
|
||||
format VARCHAR(20),
|
||||
architecture VARCHAR(20),
|
||||
format VARCHAR(30),
|
||||
architecture VARCHAR(30),
|
||||
compiler VARCHAR(100),
|
||||
file_path VARCHAR(500) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
|
||||
@ -2,8 +2,10 @@ package ai.decompile.decompile_ai;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
class DecompileAiApplicationTests {
|
||||
|
||||
@Test
|
||||
|
||||
@ -37,7 +37,15 @@ class BinaryControllerTest {
|
||||
var binaryId = UUID.randomUUID();
|
||||
var response =
|
||||
new BinaryResponse(
|
||||
binaryId, projectId, "test.exe", 1024L, null, null, "abc_test.exe", Instant.now());
|
||||
binaryId,
|
||||
projectId,
|
||||
"test.exe",
|
||||
1024L,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"abc_test.exe",
|
||||
Instant.now());
|
||||
when(binaryService.getBinaries(eq(projectId), isNull())).thenReturn(List.of(response));
|
||||
|
||||
mockMvc
|
||||
@ -72,6 +80,7 @@ class BinaryControllerTest {
|
||||
512L,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"abc_filtered.exe",
|
||||
Instant.now());
|
||||
when(binaryService.getBinaries(eq(projectId), eq("filter"))).thenReturn(List.of(response));
|
||||
@ -102,7 +111,7 @@ class BinaryControllerTest {
|
||||
new MockMultipartFile("file", "test.exe", "application/octet-stream", new byte[] {1, 2, 3});
|
||||
var response =
|
||||
new BinaryResponse(
|
||||
binaryId, projectId, "test.exe", 3L, null, null, "abc_test.exe", Instant.now());
|
||||
binaryId, projectId, "test.exe", 3L, null, null, null, "abc_test.exe", Instant.now());
|
||||
when(binaryService.uploadBinary(eq(projectId), any())).thenReturn(response);
|
||||
|
||||
mockMvc
|
||||
|
||||
@ -25,3 +25,5 @@ app:
|
||||
exchange: decompile.jobs.test
|
||||
queue: decompile.jobs.test.queue
|
||||
routing-key: decompile.jobs.test.created
|
||||
docker:
|
||||
enabled: false
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user