From ae8f5b87a63f9cc0064cfbf1637b8b820f851702 Mon Sep 17 00:00:00 2001 From: Rodrigo Verdiani Date: Sun, 7 Jun 2026 15:15:56 -0300 Subject: [PATCH] feat(die): integrate DiE analysis and result parsing --- Dockerfile.die | 14 ++ .../common/config/JacksonConfig.java | 13 ++ .../ai/decompile/die/model/DieResult.java | 20 +++ .../decompile/die/model/DieResultParser.java | 124 ++++++++++++++ .../ai/decompile/die/model/package-info.java | 2 + .../java/ai/decompile/die/package-info.java | 4 + .../ai/decompile/die/service/DiecService.java | 66 ++++++++ .../decompile/die/service/package-info.java | 2 + .../docker/config/DockerClientConfig.java | 22 +++ .../ai/decompile/docker/package-info.java | 3 + .../docker/service/ContainerService.java | 155 ++++++++++++++++++ .../java/ai/decompile/job/package-info.java | 9 +- .../service/handler/AnalyzeFileHandler.java | 38 ++++- .../workspace/model/dto/BinaryResponse.java | 2 + .../workspace/model/entity/Binary.java | 7 +- .../workspace/service/BinaryService.java | 14 ++ .../workspace/service/package-info.java | 2 + src/main/resources/application.yml | 9 + .../migration/V004__create_binaries_table.sql | 5 +- .../DecompileAiApplicationTests.java | 2 + .../controller/BinaryControllerTest.java | 13 +- src/test/resources/application-test.yml | 2 + 22 files changed, 520 insertions(+), 8 deletions(-) create mode 100644 Dockerfile.die create mode 100644 src/main/java/ai/decompile/common/config/JacksonConfig.java create mode 100644 src/main/java/ai/decompile/die/model/DieResult.java create mode 100644 src/main/java/ai/decompile/die/model/DieResultParser.java create mode 100644 src/main/java/ai/decompile/die/model/package-info.java create mode 100644 src/main/java/ai/decompile/die/package-info.java create mode 100644 src/main/java/ai/decompile/die/service/DiecService.java create mode 100644 src/main/java/ai/decompile/die/service/package-info.java create mode 100644 src/main/java/ai/decompile/docker/config/DockerClientConfig.java create mode 100644 src/main/java/ai/decompile/docker/package-info.java create mode 100644 src/main/java/ai/decompile/docker/service/ContainerService.java create mode 100644 src/main/java/ai/decompile/workspace/service/package-info.java diff --git a/Dockerfile.die b/Dockerfile.die new file mode 100644 index 0000000..7d7b3ab --- /dev/null +++ b/Dockerfile.die @@ -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"] diff --git a/src/main/java/ai/decompile/common/config/JacksonConfig.java b/src/main/java/ai/decompile/common/config/JacksonConfig.java new file mode 100644 index 0000000..4538f29 --- /dev/null +++ b/src/main/java/ai/decompile/common/config/JacksonConfig.java @@ -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(); + } +} diff --git a/src/main/java/ai/decompile/die/model/DieResult.java b/src/main/java/ai/decompile/die/model/DieResult.java new file mode 100644 index 0000000..5c05642 --- /dev/null +++ b/src/main/java/ai/decompile/die/model/DieResult.java @@ -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 detects) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Detect( + @JsonProperty("filetype") String filetype, @JsonProperty("values") List values) {} + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Value( + @JsonProperty("info") String info, + @JsonProperty("string") String string, + @JsonProperty("type") String type, + @JsonProperty("name") String name) {} +} diff --git a/src/main/java/ai/decompile/die/model/DieResultParser.java b/src/main/java/ai/decompile/die/model/DieResultParser.java new file mode 100644 index 0000000..063ce48 --- /dev/null +++ b/src/main/java/ai/decompile/die/model/DieResultParser.java @@ -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 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) {} +} diff --git a/src/main/java/ai/decompile/die/model/package-info.java b/src/main/java/ai/decompile/die/model/package-info.java new file mode 100644 index 0000000..e7f36fb --- /dev/null +++ b/src/main/java/ai/decompile/die/model/package-info.java @@ -0,0 +1,2 @@ +@org.springframework.modulith.NamedInterface("model") +package ai.decompile.die.model; diff --git a/src/main/java/ai/decompile/die/package-info.java b/src/main/java/ai/decompile/die/package-info.java new file mode 100644 index 0000000..82cead5 --- /dev/null +++ b/src/main/java/ai/decompile/die/package-info.java @@ -0,0 +1,4 @@ +@org.springframework.modulith.ApplicationModule( + displayName = "DiE", + allowedDependencies = {"docker", "common"}) +package ai.decompile.die; diff --git a/src/main/java/ai/decompile/die/service/DiecService.java b/src/main/java/ai/decompile/die/service/DiecService.java new file mode 100644 index 0000000..25a215c --- /dev/null +++ b/src/main/java/ai/decompile/die/service/DiecService.java @@ -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); + } + } +} diff --git a/src/main/java/ai/decompile/die/service/package-info.java b/src/main/java/ai/decompile/die/service/package-info.java new file mode 100644 index 0000000..395c5b8 --- /dev/null +++ b/src/main/java/ai/decompile/die/service/package-info.java @@ -0,0 +1,2 @@ +@org.springframework.modulith.NamedInterface("service") +package ai.decompile.die.service; diff --git a/src/main/java/ai/decompile/docker/config/DockerClientConfig.java b/src/main/java/ai/decompile/docker/config/DockerClientConfig.java new file mode 100644 index 0000000..2d74cce --- /dev/null +++ b/src/main/java/ai/decompile/docker/config/DockerClientConfig.java @@ -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(); + } +} diff --git a/src/main/java/ai/decompile/docker/package-info.java b/src/main/java/ai/decompile/docker/package-info.java new file mode 100644 index 0000000..0d7e022 --- /dev/null +++ b/src/main/java/ai/decompile/docker/package-info.java @@ -0,0 +1,3 @@ +@org.springframework.modulith.ApplicationModule( + type = org.springframework.modulith.ApplicationModule.Type.OPEN) +package ai.decompile.docker; diff --git a/src/main/java/ai/decompile/docker/service/ContainerService.java b/src/main/java/ai/decompile/docker/service/ContainerService.java new file mode 100644 index 0000000..43adc86 --- /dev/null +++ b/src/main/java/ai/decompile/docker/service/ContainerService.java @@ -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 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 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(); + } + } +} diff --git a/src/main/java/ai/decompile/job/package-info.java b/src/main/java/ai/decompile/job/package-info.java index 0393e3b..df0dadb 100644 --- a/src/main/java/ai/decompile/job/package-info.java +++ b/src/main/java/ai/decompile/job/package-info.java @@ -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; diff --git a/src/main/java/ai/decompile/job/service/handler/AnalyzeFileHandler.java b/src/main/java/ai/decompile/job/service/handler/AnalyzeFileHandler.java index 34867c1..73d3464 100644 --- a/src/main/java/ai/decompile/job/service/handler/AnalyzeFileHandler.java +++ b/src/main/java/ai/decompile/job/service/handler/AnalyzeFileHandler.java @@ -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; + } + } } diff --git a/src/main/java/ai/decompile/workspace/model/dto/BinaryResponse.java b/src/main/java/ai/decompile/workspace/model/dto/BinaryResponse.java index c441d54..9564b9a 100644 --- a/src/main/java/ai/decompile/workspace/model/dto/BinaryResponse.java +++ b/src/main/java/ai/decompile/workspace/model/dto/BinaryResponse.java @@ -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()); } diff --git a/src/main/java/ai/decompile/workspace/model/entity/Binary.java b/src/main/java/ai/decompile/workspace/model/entity/Binary.java index f11a530..93e3996 100644 --- a/src/main/java/ai/decompile/workspace/model/entity/Binary.java +++ b/src/main/java/ai/decompile/workspace/model/entity/Binary.java @@ -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; diff --git a/src/main/java/ai/decompile/workspace/service/BinaryService.java b/src/main/java/ai/decompile/workspace/service/BinaryService.java index f2fc792..7ff027a 100644 --- a/src/main/java/ai/decompile/workspace/service/BinaryService.java +++ b/src/main/java/ai/decompile/workspace/service/BinaryService.java @@ -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) diff --git a/src/main/java/ai/decompile/workspace/service/package-info.java b/src/main/java/ai/decompile/workspace/service/package-info.java new file mode 100644 index 0000000..9d44c08 --- /dev/null +++ b/src/main/java/ai/decompile/workspace/service/package-info.java @@ -0,0 +1,2 @@ +@org.springframework.modulith.NamedInterface("services") +package ai.decompile.workspace.service; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8dd8ecf..9e07e23 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -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: diff --git a/src/main/resources/db/migration/V004__create_binaries_table.sql b/src/main/resources/db/migration/V004__create_binaries_table.sql index 2f94416..5dd9562 100644 --- a/src/main/resources/db/migration/V004__create_binaries_table.sql +++ b/src/main/resources/db/migration/V004__create_binaries_table.sql @@ -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 diff --git a/src/test/java/ai/decompile/decompile_ai/DecompileAiApplicationTests.java b/src/test/java/ai/decompile/decompile_ai/DecompileAiApplicationTests.java index 302502a..d65aa35 100644 --- a/src/test/java/ai/decompile/decompile_ai/DecompileAiApplicationTests.java +++ b/src/test/java/ai/decompile/decompile_ai/DecompileAiApplicationTests.java @@ -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 diff --git a/src/test/java/ai/decompile/workspace/controller/BinaryControllerTest.java b/src/test/java/ai/decompile/workspace/controller/BinaryControllerTest.java index 3a7be82..4bea2d8 100644 --- a/src/test/java/ai/decompile/workspace/controller/BinaryControllerTest.java +++ b/src/test/java/ai/decompile/workspace/controller/BinaryControllerTest.java @@ -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 diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 78e942f..cb3c97c 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -25,3 +25,5 @@ app: exchange: decompile.jobs.test queue: decompile.jobs.test.queue routing-key: decompile.jobs.test.created + docker: + enabled: false