diff options
| author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2021-12-19 22:56:09 +0100 |
|---|---|---|
| committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2021-12-28 00:45:53 +0100 |
| commit | 4e8423db22a77af394bb519e2a828714ab48898d (patch) | |
| tree | 91cc55474c67c5be7507080d264cd0f9cff495c2 | |
| parent | 796b19da1b9ef6c1721faa2ddf35100eb01a8a28 (diff) | |
Add jgvariant-ostree module.
Change-Id: Idf7bacad28d7cf65eb1ddd0994dcc2c2c2a7e18e
57 files changed, 925 insertions, 2 deletions
@@ -61,6 +61,10 @@ pairs of [String][] and `int`, you can use the following code: <groupId>eu.mulk.jgvariant</groupId> <artifactId>jgvariant-core</artifactId> </dependency> + <dependency> + <groupId>eu.mulk.jgvariant</groupId> + <artifactId>jgvariant-ostree</artifactId> + </dependency> ... </dependencies> @@ -76,6 +80,7 @@ pairs of [String][] and `int`, you can use the following code: implementation(platform("eu.mulk.jgvariant:jgvariant-bom:0.1.4") implementation("eu.mulk.jgvariant:jgvariant-core") + implementation("eu.mulk.jgvariant:jgvariant-ostree") ... } diff --git a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java index d2f2403..fc11eab 100644 --- a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java +++ b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.function.Function; import org.apiguardian.api.API; import org.apiguardian.api.API.Status; import org.jetbrains.annotations.Nullable; @@ -80,7 +81,7 @@ public abstract class Decoder<T> { * @param byteOrder the byte order to use. * @return a new, decorated {@link Decoder}. */ - public Decoder<T> withByteOrder(ByteOrder byteOrder) { + public final Decoder<T> withByteOrder(ByteOrder byteOrder) { var delegate = this; return new Decoder<>() { @@ -103,6 +104,34 @@ public abstract class Decoder<T> { } /** + * Creates a new {@link Decoder} from an existing one by applying a function to the result. + * + * @param function the function to apply. + * @return a new, decorated {@link Decoder}. + * @see java.util.stream.Stream#map + */ + public final <U> Decoder<U> map(Function<T, U> function) { + var delegate = this; + + return new Decoder<>() { + @Override + public byte alignment() { + return delegate.alignment(); + } + + @Override + public @Nullable Integer fixedSize() { + return delegate.fixedSize(); + } + + @Override + public U decode(ByteBuffer byteSlice) { + return function.apply(delegate.decode(byteSlice)); + } + }; + } + + /** * Creates a {@link Decoder} for an {@code Array} type. * * @param elementDecoder a {@link Decoder} for the elements of the array. @@ -114,6 +143,16 @@ public abstract class Decoder<T> { } /** + * Creates a {@link Decoder} for an {@code Array} type of element type {@code byte} into a + * primitive {@code byte[]} array. + * + * @return a new {@link Decoder}. + */ + public static Decoder<byte[]> ofByteArray() { + return new ByteArrayDecoder(); + } + + /** * Creates a {@link Decoder} for a {@code Maybe} type. * * @param elementDecoder a {@link Decoder} for the contained element. @@ -299,6 +338,9 @@ public abstract class Decoder<T> { var element = elementDecoder.decode(byteSlice.slice(i, elementSize)); elements.add(element); } + } else if (byteSlice.limit() == 0) { + // A degenerate zero-length array of variable width. + elements = List.of(); } else { // An array with aligned elements and a vector of framing offsets in the end. int framingOffsetSize = byteCount(byteSlice.limit()); @@ -321,6 +363,30 @@ public abstract class Decoder<T> { } } + private static class ByteArrayDecoder extends Decoder<byte[]> { + + private static final int ELEMENT_SIZE = 1; + + @Override + public byte alignment() { + return ELEMENT_SIZE; + } + + @Override + @Nullable + Integer fixedSize() { + return null; + } + + @Override + public byte[] decode(ByteBuffer byteSlice) { + // A simple C-style array. + byte[] elements = new byte[byteSlice.limit() / ELEMENT_SIZE]; + byteSlice.get(elements); + return elements; + } + } + private static class MaybeDecoder<U> extends Decoder<Optional<U>> { private final Decoder<U> elementDecoder; diff --git a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java index d9de5f1..78e4bdd 100644 --- a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java +++ b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java @@ -43,7 +43,7 @@ public final class Signature { return new Signature(signatureBytes); } - static Signature parse(String signatureString) throws ParseException { + public static Signature parse(String signatureString) throws ParseException { var signatureBytes = ByteBuffer.wrap(signatureString.getBytes(StandardCharsets.US_ASCII)); return parse(signatureBytes); } diff --git a/jgvariant-core/src/main/java/module-info.java b/jgvariant-core/src/main/java/module-info.java index a1830f6..0ef35cc 100644 --- a/jgvariant-core/src/main/java/module-info.java +++ b/jgvariant-core/src/main/java/module-info.java @@ -49,6 +49,10 @@ * <groupId>eu.mulk.jgvariant</groupId> * <artifactId>jgvariant-core</artifactId> * </dependency> + * <dependency> + * <groupId>eu.mulk.jgvariant</groupId> + * <artifactId>jgvariant-ostree</artifactId> + * </dependency> * * ... * </dependencies> @@ -65,6 +69,7 @@ * * implementation(platform("eu.mulk.jgvariant:jgvariant-bom:0.1.4") * implementation("eu.mulk.jgvariant:jgvariant-core") + * implementation("eu.mulk.jgvariant:jgvariant-ostree") * * ... * } diff --git a/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java b/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java index 5cf1a1c..d72a1b6 100644 --- a/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java +++ b/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java @@ -242,6 +242,26 @@ class DecoderTest { } @Test + void testPrimitiveByteArray() { + var data = new byte[] {0x04, 0x05, 0x06, 0x07}; + + var decoder = Decoder.ofByteArray(); + + assertArrayEquals(data, decoder.decode(ByteBuffer.wrap(data))); + } + + @Test + void testPrimitiveByteArrayRecord() { + var data = new byte[] {0x04, 0x05, 0x06, 0x07}; + + record TestRecord(byte[] bytes) {} + + var decoder = Decoder.ofStructure(TestRecord.class, Decoder.ofByteArray()); + + assertArrayEquals(data, decoder.decode(ByteBuffer.wrap(data)).bytes()); + } + + @Test void testIntegerArray() { var data = new byte[] {0x04, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00}; @@ -434,4 +454,11 @@ class DecoderTest { var signature = Signature.parse(ByteBuffer.wrap(data)); assertEquals("(bynqiuxtdsogvmiai)", signature.toString()); } + + @Test + void testMap() { + var data = new byte[] {0x0A, 0x0B, 0x0C}; + var decoder = Decoder.ofByteArray().map(bytes -> bytes.length); + assertEquals(3, decoder.decode(ByteBuffer.wrap(data))); + } } diff --git a/jgvariant-ostree/pom.xml b/jgvariant-ostree/pom.xml new file mode 100644 index 0000000..f7da288 --- /dev/null +++ b/jgvariant-ostree/pom.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + + <modelVersion>4.0.0</modelVersion> + + <version>0.1.4-SNAPSHOT</version> + + <artifactId>jgvariant-ostree</artifactId> + <packaging>jar</packaging> + + <name>JGVariant OSTree Parser</name> + <url>https://gerrit.benkard.de/plugins/gitiles/jgvariant</url> + + <description> + GVariant serialization and deserialization. + </description> + + <parent> + <groupId>eu.mulk.jgvariant</groupId> + <artifactId>jgvariant-parent</artifactId> + <version>0.1.4-SNAPSHOT</version> + <relativePath>../jgvariant-parent/pom.xml</relativePath> + </parent> + + <dependencies> + <!-- JGVariant --> + <dependency> + <groupId>eu.mulk.jgvariant</groupId> + <artifactId>jgvariant-core</artifactId> + <version>0.1.4-SNAPSHOT</version> + </dependency> + + <!-- Annotations --> + <dependency> + <groupId>com.google.errorprone</groupId> + <artifactId>error_prone_annotations</artifactId> + </dependency> + <dependency> + <groupId>org.jetbrains</groupId> + <artifactId>annotations</artifactId> + </dependency> + <dependency> + <groupId>org.apiguardian</groupId> + <artifactId>apiguardian-api</artifactId> + </dependency> + + <!-- Testing --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.hosuaby</groupId> + <artifactId>inject-resources-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java new file mode 100644 index 0000000..1a85547 --- /dev/null +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java @@ -0,0 +1,41 @@ +package eu.mulk.jgvariant.ostree; + +import eu.mulk.jgvariant.core.Decoder; +import java.util.Arrays; +import java.util.HexFormat; + +/** + * A wrapper for a {@code byte[]} that implements {@link #equals(Object)}, {@link #hashCode()}, and + * {@link #toString()} according to value semantics. + */ +public record ByteString(byte[] bytes) { + + private static final Decoder<ByteString> DECODER = Decoder.ofByteArray().map(ByteString::new); + + public static Decoder<ByteString> decoder() { + return DECODER; + } + + @Override + public boolean equals(Object o) { + return (o instanceof ByteString byteString) && Arrays.equals(bytes, byteString.bytes); + } + + @Override + public int hashCode() { + return Arrays.hashCode(bytes); + } + + @Override + public String toString() { + return "ByteString{hex=\"%s\"}".formatted(hex()); + } + + public String hex() { + return HexFormat.of().formatHex(bytes); + } + + public static ByteString ofHex(String hex) { + return new ByteString(HexFormat.of().parseHex(hex)); + } +} diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Checksum.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Checksum.java new file mode 100644 index 0000000..f77eb57 --- /dev/null +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Checksum.java @@ -0,0 +1,24 @@ +package eu.mulk.jgvariant.ostree; + +import eu.mulk.jgvariant.core.Decoder; + +/** + * A wrapper for {@link ByteString} that refers to a content-addressed object in an OSTree + * repository. + */ +public record Checksum(ByteString bytes) { + + private static final Decoder<Checksum> DECODER = ByteString.decoder().map(Checksum::new); + + public static Decoder<Checksum> decoder() { + return DECODER; + } + + public String hex() { + return bytes.hex(); + } + + public static Checksum ofHex(String hex) { + return new Checksum(ByteString.ofHex(hex)); + } +} diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Commit.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Commit.java new file mode 100644 index 0000000..43909ba --- /dev/null +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Commit.java @@ -0,0 +1,51 @@ +package eu.mulk.jgvariant.ostree; + +import eu.mulk.jgvariant.core.Decoder; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * A commit in an OSTree repository. + * + * <p>Has an optional parent, a root directory, and various metadata. + * + * <p>Reference: {@code ostree-core.h#OSTREE_COMMIT_GVARIANT_STRING} + */ +public record Commit( + Metadata metadata, + Checksum parentChecksum, + List<RelatedObject> relatedObjects, + String subject, + String body, + long timestamp, + Checksum rootDirTreeChecksum, + Checksum rootDirMetaChecksum) { + + public record RelatedObject(String ref, Checksum commitChecksum) { + + private static final Decoder<RelatedObject> DECODER = + Decoder.ofStructure( + RelatedObject.class, Decoder.ofString(StandardCharsets.UTF_8), Checksum.decoder()); + + public static Decoder<RelatedObject> decoder() { + return DECODER; + } + } + + private static final Decoder<Commit> DECODER = + Decoder.ofStructure( + Commit.class, + Metadata.decoder(), + Checksum.decoder(), + Decoder.ofArray(RelatedObject.decoder()), + Decoder.ofString(StandardCharsets.UTF_8), + Decoder.ofString(StandardCharsets.UTF_8), + Decoder.ofLong().withByteOrder(ByteOrder.BIG_ENDIAN), + Checksum.decoder(), + Checksum.decoder()); + + public static Decoder<Commit> decoder() { + return DECODER; + } +} diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaFallback.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaFallback.java new file mode 100644 index 0000000..1b3cc0a --- /dev/null +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaFallback.java @@ -0,0 +1,25 @@ +package eu.mulk.jgvariant.ostree; + +import eu.mulk.jgvariant.core.Decoder; +import java.nio.ByteOrder; + +/** + * A fallback entry in a {@link DeltaSuperblock}. + * + * <p>Reference: {@code ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_FALLBACK_FORMAT} + */ +public record DeltaFallback( + byte objectType, Checksum checksum, long compressedSize, long uncompressedSize) { + + private static final Decoder<DeltaFallback> DECODER = + Decoder.ofStructure( + DeltaFallback.class, + Decoder.ofByte(), + Checksum.decoder(), + Decoder.ofLong().withByteOrder(ByteOrder.LITTLE_ENDIAN), // FIXME: non-canonical + Decoder.ofLong().withByteOrder(ByteOrder.LITTLE_ENDIAN)); // FIXME: non-canonical + + public static Decoder<DeltaFallback> decoder() { + return DECODER; + } +} diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaMetaEntry.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaMetaEntry.java new file mode 100644 index 0000000..39ada86 --- /dev/null +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaMetaEntry.java @@ -0,0 +1,38 @@ +package eu.mulk.jgvariant.ostree; + +import eu.mulk.jgvariant.core.Decoder; +import java.nio.ByteOrder; +import java.util.List; + +/** + * An entry in a {@link DeltaSuperblock}. + * + * <p>Reference: {@code ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_META_ENTRY_FORMAT} + */ +public record DeltaMetaEntry( + int version, Checksum checksum, long size, long usize, List<DeltaObject> objects) { + + record DeltaObject(byte objectType, Checksum checksum) { + + private static final Decoder<DeltaObject> DECODER = + Decoder.ofStructure(DeltaObject.class, Decoder.ofByte(), Checksum.decoder()); + + public static Decoder<DeltaObject> decoder() { + return DECODER; + } + } + + private static final Decoder<DeltaMetaEntry> DECODER = + Decoder.ofStructure( + DeltaMetaEntry.class, + Decoder.ofInt().withByteOrder(ByteOrder.LITTLE_ENDIAN), // FIXME: non-canonical + Checksum.decoder(), + Decoder.ofLong().withByteOrder(ByteOrder.LITTLE_ENDIAN), // FIXME: non-canonical + Decoder.ofLong().withByteOrder(ByteOrder.LITTLE_ENDIAN), // FIXME: non-canonical + Decoder.ofByteArray().map(x -> List.of()) // FIXME + ); + + public static Decoder<DeltaMetaEntry> decoder() { + return DECODER; + } |
