Skip to content

Commit 395df42

Browse files
authored
Hash native libraries (#8)
This PR changes the way native libraries are enumerated in the ResourceInformation.json file. Previously, we used a list of filenames. We now use a map of filenames:MD5_hash. This allows us to check the hashes of native libraries as they're extracted.
1 parent 0722e52 commit 395df42

File tree

2 files changed

+67
-30
lines changed

2 files changed

+67
-30
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ name: CI
33
on: [push, pull_request]
44

55
jobs:
6+
lint:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v6
10+
- uses: actions/setup-java@v5
11+
with:
12+
java-version: 17
13+
distribution: 'temurin'
14+
- name: Check for changes
15+
run: ./gradlew spotlessCheck
16+
617
build:
718
strategy:
819
matrix:
Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
package org.photonvision.tools;
22

33
import com.google.gson.GsonBuilder;
4+
import java.io.BufferedInputStream;
45
import java.io.File;
56
import java.io.FileInputStream;
67
import java.io.IOException;
8+
import java.io.OutputStream;
79
import java.nio.charset.Charset;
810
import java.nio.file.Files;
911
import java.nio.file.Path;
12+
import java.security.DigestInputStream;
1013
import java.security.MessageDigest;
1114
import java.security.NoSuchAlgorithmException;
12-
import java.util.ArrayList;
1315
import java.util.HashMap;
16+
import java.util.HexFormat;
1417
import java.util.List;
1518
import java.util.Map;
1619
import javax.inject.Inject;
17-
import org.codehaus.groovy.runtime.EncodingGroovyMethods;
1820
import org.gradle.api.DefaultTask;
1921
import org.gradle.api.file.Directory;
2022
import org.gradle.api.file.DirectoryProperty;
@@ -26,6 +28,28 @@
2628
import org.gradle.api.tasks.TaskAction;
2729

2830
public class HashNativeResources extends DefaultTask {
31+
32+
/**
33+
* Architecture-specific information containing file hashes for a specific CPU architecture (e.g.,
34+
* x86-64, arm64).
35+
*/
36+
public record ArchInfo(Map<String, String> fileHashes) {}
37+
38+
/**
39+
* Platform-specific information containing architectures for a specific OS platform (e.g., linux,
40+
* windows).
41+
*/
42+
public record PlatformInfo(Map<String, ArchInfo> architectures) {}
43+
44+
/** Overall resource information to be serialized */
45+
public record ResourceInformation(
46+
// Combined MD5 hash of all native resource files
47+
String hash,
48+
// Platform-specific native libraries organized by platform then architecture
49+
Map<String, PlatformInfo> platforms,
50+
// List of supported versions for these native resources
51+
List<String> versions) {}
52+
2953
private final DirectoryProperty inputDirectory;
3054
private final RegularFileProperty hashFile;
3155
private final RegularFileProperty versionsInput;
@@ -55,16 +79,13 @@ public HashNativeResources() {
5579

5680
@TaskAction
5781
public void execute() throws NoSuchAlgorithmException, IOException {
58-
MessageDigest hash = MessageDigest.getInstance("MD5");
82+
MessageDigest combinedHash = MessageDigest.getInstance("MD5");
5983

6084
Directory directory = inputDirectory.get();
6185

6286
Path inputPath = directory.getAsFile().toPath();
6387

64-
Map<String, Object> platforms = new HashMap<>();
65-
66-
byte[] buffer = new byte[0xFFFF];
67-
int readBytes = 0;
88+
Map<String, PlatformInfo> platforms = new HashMap<>();
6889

6990
for (File file : directory.getAsFileTree()) {
7091
if (!file.isFile()) {
@@ -73,44 +94,49 @@ public void execute() throws NoSuchAlgorithmException, IOException {
7394

7495
Path path = inputPath.relativize(file.toPath());
7596

76-
try (FileInputStream is = new FileInputStream(file)) {
77-
while ((readBytes = is.read(buffer)) != -1) {
78-
hash.update(buffer, 0, readBytes);
79-
}
97+
// Compute individual file hash
98+
MessageDigest fileHash = MessageDigest.getInstance("MD5");
99+
try (var dis =
100+
new DigestInputStream(
101+
new DigestInputStream(new BufferedInputStream(new FileInputStream(file)), fileHash),
102+
combinedHash)) {
103+
dis.transferTo(OutputStream.nullOutputStream());
80104
}
81105

82106
String platform = path.getName(0).toString();
83107
String arch = path.getName(1).toString();
84108

85109
String strPath = "/" + path.toString().replace("\\", "/");
86-
87-
@SuppressWarnings("unchecked") // This will always be the correct type
88-
Map<String, List<String>> platformMap = (Map<String, List<String>>) platforms.get(platform);
89-
if (platformMap == null) {
90-
platformMap = new HashMap<>();
91-
List<String> archFiles = new ArrayList<>();
92-
archFiles.add(strPath);
93-
platformMap.put(arch, archFiles);
94-
platforms.put(platform, platformMap);
110+
String hexFileHash = HexFormat.of().formatHex(fileHash.digest());
111+
112+
PlatformInfo platformInfo = platforms.get(platform);
113+
if (platformInfo == null) {
114+
Map<String, String> fileHashes = new HashMap<>();
115+
fileHashes.put(strPath, hexFileHash);
116+
Map<String, ArchInfo> architectures = new HashMap<>();
117+
architectures.put(arch, new ArchInfo(fileHashes));
118+
platforms.put(platform, new PlatformInfo(architectures));
95119
} else {
96-
List<String> archFiles = platformMap.get(arch);
97-
if (archFiles == null) {
98-
archFiles = new ArrayList<>();
99-
archFiles.add(strPath);
100-
platformMap.put(arch, archFiles);
120+
Map<String, ArchInfo> architectures = platformInfo.architectures();
121+
ArchInfo archInfo = architectures.get(arch);
122+
if (archInfo == null) {
123+
Map<String, String> fileHashes = new HashMap<>();
124+
fileHashes.put(strPath, hexFileHash);
125+
architectures.put(arch, new ArchInfo(fileHashes));
101126
} else {
102-
archFiles.add(strPath);
127+
archInfo.fileHashes().put(strPath, hexFileHash);
103128
}
104129
}
105130
}
106131

107-
var versions = Files.readAllLines(versionsInput.get().getAsFile().toPath());
132+
String hash = HexFormat.of().formatHex(combinedHash.digest());
133+
134+
List<String> versions = Files.readAllLines(versionsInput.get().getAsFile().toPath());
135+
ResourceInformation output = new ResourceInformation(hash, platforms, versions);
108136

109-
platforms.put("hash", EncodingGroovyMethods.encodeHex(hash.digest()).toString());
110-
platforms.put("versions", versions);
111137
GsonBuilder builder = new GsonBuilder();
112138
builder.setPrettyPrinting();
113-
var json = builder.create().toJson(platforms);
139+
var json = builder.create().toJson(output);
114140
Files.writeString(hashFile.get().getAsFile().toPath(), json, Charset.defaultCharset());
115141
}
116142
}

0 commit comments

Comments
 (0)