Properly document jgvariant-ostree.
Change-Id: I0aa3b1df512ef99d0e25d73efdd34a1b488e7d0d
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
index 1a85547..cf6e99a 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java
@@ -3,15 +3,24 @@
import eu.mulk.jgvariant.core.Decoder;
import java.util.Arrays;
import java.util.HexFormat;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
/**
* A wrapper for a {@code byte[]} that implements {@link #equals(Object)}, {@link #hashCode()}, and
* {@link #toString()} according to value semantics.
+ *
+ * @param bytes the byte array that this ByteString wraps.
*/
public record ByteString(byte[] bytes) {
private static final Decoder<ByteString> DECODER = Decoder.ofByteArray().map(ByteString::new);
+ /**
+ * Returns a decoder for a {@code byte[]} that wraps the result in {@link ByteString}.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<ByteString> decoder() {
return DECODER;
}
@@ -31,11 +40,42 @@
return "ByteString{hex=\"%s\"}".formatted(hex());
}
+ /**
+ * Converts the contained byte array into a hex string.
+ *
+ * <p>Useful for printing.
+ *
+ * @return a hex string representation of this byte string.
+ */
public String hex() {
return HexFormat.of().formatHex(bytes);
}
+ /**
+ * Parses a hex string into a {@link ByteString}.
+ *
+ * @param hex a hex string.
+ * @return a {@link ByteString} corresponding to the given hex string.
+ */
public static ByteString ofHex(String hex) {
return new ByteString(HexFormat.of().parseHex(hex));
}
+
+ /**
+ * Returns the number of bytes in the byte string.
+ *
+ * @return the number of bytes in the byte string.
+ */
+ public int size() {
+ return bytes.length;
+ }
+
+ /**
+ * Returns a {@link Stream} of all the bytes in the byte string.
+ *
+ * @return a new {@link Stream}.
+ */
+ Stream<Byte> stream() {
+ return IntStream.range(0, bytes.length).mapToObj(i -> bytes[i]);
+ }
}
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
index f77eb57..705e27c 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Checksum.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Checksum.java
@@ -1,24 +1,85 @@
package eu.mulk.jgvariant.ostree;
import eu.mulk.jgvariant.core.Decoder;
+import java.nio.ByteBuffer;
/**
* A wrapper for {@link ByteString} that refers to a content-addressed object in an OSTree
* repository.
+ *
+ * @param byteString the bytes that make up this {@link Checksum}.
*/
-public record Checksum(ByteString bytes) {
+public record Checksum(ByteString byteString) {
+
+ private static final int SIZE = 32;
private static final Decoder<Checksum> DECODER = ByteString.decoder().map(Checksum::new);
+ public Checksum {
+ if (byteString.size() != SIZE) {
+ throw new IllegalArgumentException(
+ "attempted to construct Checksum of length %d (expected: %d)"
+ .formatted(byteString.size(), SIZE));
+ }
+ }
+
+ /**
+ * A decoder for a {@code byte[]} that wraps the result in a {@link Checksum}.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<Checksum> decoder() {
return DECODER;
}
- public String hex() {
- return bytes.hex();
+ /**
+ * Returns an empty checksum.
+ *
+ * @return a checksum whose bits are all zero.
+ */
+ public static Checksum zero() {
+ return new Checksum(new ByteString(new byte[SIZE]));
}
+ /**
+ * Checks whether the checksum contains only zero bits.
+ *
+ * @return {@code true} if the byte string is equal to {@link #zero()}, {@code false} otherwise.
+ */
+ public boolean isEmpty() {
+ return equals(zero());
+ }
+
+ /**
+ * Converts the contained byte array into a hex string.
+ *
+ * <p>Useful for printing.
+ *
+ * @return a hex string representation of the bytes making up this checksum.
+ */
+ public String hex() {
+ return byteString.hex();
+ }
+
+ /**
+ * Parses a hex string into a {@link Checksum}.
+ *
+ * @param hex a hex string.
+ * @return a {@link Checksum} corresponding to the given hex string.
+ */
public static Checksum ofHex(String hex) {
return new Checksum(ByteString.ofHex(hex));
}
+
+ /**
+ * Reads a Checksum for a {@link ByteBuffer}.
+ *
+ * @param byteBuffer the byte buffer to read from.
+ * @return a checksum.
+ */
+ public static Checksum readFrom(ByteBuffer byteBuffer) {
+ var bytes = new byte[SIZE];
+ byteBuffer.get(bytes);
+ return new Checksum(new ByteString(bytes));
+ }
}
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
index 43909ba..a250937 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Commit.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Commit.java
@@ -11,6 +11,19 @@
* <p>Has an optional parent, a root directory, and various metadata.
*
* <p>Reference: {@code ostree-core.h#OSTREE_COMMIT_GVARIANT_STRING}
+ *
+ * @param metadata arbitrary metadata supplied by the user who made the commit.
+ * @param parentChecksum a (possibly {@link Checksum#isEmpty()}) reference to this commit's parent
+ * commit.
+ * @param relatedObjects references to related commits.
+ * @param subject the subject line part of the commit message.
+ * @param body the body part of the commit message.
+ * @param timestamp UNIX epoch seconds of when the commit was done.
+ * @param rootDirTreeChecksum the checksum of the {@link DirTree} file describing the root
+ * directory.
+ * @param rootDirMetaChecksum the checksum of the {@link DirMeta} file describing the root
+ * directory.
+ * @see ObjectType#COMMIT
*/
public record Commit(
Metadata metadata,
@@ -22,6 +35,12 @@
Checksum rootDirTreeChecksum,
Checksum rootDirMetaChecksum) {
+ /**
+ * A reference to a related commit.
+ *
+ * @param ref the name of the reference.
+ * @param commitChecksum the checksum of the related commit.
+ */
public record RelatedObject(String ref, Checksum commitChecksum) {
private static final Decoder<RelatedObject> DECODER =
@@ -45,6 +64,11 @@
Checksum.decoder(),
Checksum.decoder());
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link 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
index 1b3cc0a..46470d4 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaFallback.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaFallback.java
@@ -6,19 +6,31 @@
/**
* A fallback entry in a {@link DeltaSuperblock}.
*
+ * <p>References a file in the OSTree repository.
+ *
* <p>Reference: {@code ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_FALLBACK_FORMAT}
+ *
+ * @param objectType the object type of the file represented by this fallback entry.
+ * @param checksum the checksum of the file represented by this fallback entry.
+ * @param compressedSize the compressed size of the file represented by this fallback entry.
+ * @param uncompressedSize the uncompressed size of the file represented by this fallback entry.
*/
public record DeltaFallback(
- byte objectType, Checksum checksum, long compressedSize, long uncompressedSize) {
+ ObjectType objectType, Checksum checksum, long compressedSize, long uncompressedSize) {
private static final Decoder<DeltaFallback> DECODER =
Decoder.ofStructure(
DeltaFallback.class,
- Decoder.ofByte(),
+ Decoder.ofByte().map(ObjectType::valueOf),
Checksum.decoder(),
Decoder.ofLong().withByteOrder(ByteOrder.LITTLE_ENDIAN), // FIXME: non-canonical
Decoder.ofLong().withByteOrder(ByteOrder.LITTLE_ENDIAN)); // FIXME: non-canonical
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
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
index 39ada86..0eaea73 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaMetaEntry.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaMetaEntry.java
@@ -1,22 +1,46 @@
package eu.mulk.jgvariant.ostree;
import eu.mulk.jgvariant.core.Decoder;
+import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
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}
+ *
+ * @param version the version corresponding to the version of {@link DeltaPartPayload}; always 0.
+ * @param checksum the checksum of the {@link DeltaPartPayload}.
+ * @param compressedSize the total compressed size of the delta.
+ * @param uncompressedSize the total uncompressed size of the files generated by the delta.
+ * @param objects a list of objects generated by the delta payload.
*/
public record DeltaMetaEntry(
- int version, Checksum checksum, long size, long usize, List<DeltaObject> objects) {
+ int version,
+ Checksum checksum,
+ long compressedSize,
+ long uncompressedSize,
+ List<DeltaObject> objects) {
- record DeltaObject(byte objectType, Checksum checksum) {
+ /**
+ * A reference to an object generated by a {@link DeltaPartPayload}.
+ *
+ * @param objectType the file type.
+ * @param checksum the checksum of the resulting file.
+ */
+ public record DeltaObject(ObjectType objectType, Checksum checksum) {
private static final Decoder<DeltaObject> DECODER =
- Decoder.ofStructure(DeltaObject.class, Decoder.ofByte(), Checksum.decoder());
+ Decoder.ofStructure(
+ DeltaObject.class, Decoder.ofByte().map(ObjectType::valueOf), Checksum.decoder());
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<DeltaObject> decoder() {
return DECODER;
}
@@ -29,9 +53,26 @@
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
- );
+ Decoder.ofByteArray().map(DeltaMetaEntry::parseObjectList));
+ private static List<DeltaObject> parseObjectList(byte[] bytes) {
+ var byteBuffer = ByteBuffer.wrap(bytes);
+ List<DeltaObject> objects = new ArrayList<>();
+
+ while (byteBuffer.hasRemaining()) {
+ var type = ObjectType.valueOf(byteBuffer.get());
+ var checksum = Checksum.readFrom(byteBuffer);
+ objects.add(new DeltaObject(type, checksum));
+ }
+
+ return objects;
+ }
+
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<DeltaMetaEntry> decoder() {
return DECODER;
}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaOperation.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaOperation.java
new file mode 100644
index 0000000..c1f2701
--- /dev/null
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaOperation.java
@@ -0,0 +1,53 @@
+package eu.mulk.jgvariant.ostree;
+
+import static org.apiguardian.api.API.Status.STABLE;
+
+import org.apiguardian.api.API;
+
+/** An operation in a static delta. */
+@API(status = STABLE)
+public enum DeltaOperation {
+ OPEN_SPLICE_AND_CLOSE((byte) 'S'),
+ OPEN((byte) 'o'),
+ WRITE((byte) 'w'),
+ SET_READ_SOURCE((byte) 'r'),
+ UNSET_READ_SOURCE((byte) 'R'),
+ CLOSE((byte) 'c'),
+ BSPATCH((byte) 'B');
+
+ private final byte byteValue;
+
+ DeltaOperation(byte byteValue) {
+ this.byteValue = byteValue;
+ }
+
+ /**
+ * The serialized byte value.
+ *
+ * @return a serialized byte value for use in GVariant structures.
+ */
+ public byte byteValue() {
+ return byteValue;
+ }
+
+ /**
+ * Returns the {@link DeltaOperation} corresponding to a serialized GVariant value.
+ *
+ * @param byteValue a serialized value as used in GVariant.
+ * @return the {@link DeltaOperation} corresponding to the serialized value.
+ * @throws IllegalArgumentException if the byte value is invalid.
+ */
+ public static DeltaOperation valueOf(byte byteValue) {
+ return switch (byteValue) {
+ case (byte) 'S' -> OPEN_SPLICE_AND_CLOSE;
+ case (byte) 'o' -> OPEN;
+ case (byte) 'w' -> WRITE;
+ case (byte) 'r' -> SET_READ_SOURCE;
+ case (byte) 'R' -> UNSET_READ_SOURCE;
+ case (byte) 'c' -> CLOSE;
+ case (byte) 'B' -> BSPATCH;
+ default -> throw new IllegalArgumentException(
+ "invalid DeltaOperation: %d".formatted(byteValue));
+ };
+ }
+}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaPartPayload.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaPartPayload.java
index c8c5fe7..7f7dd23 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaPartPayload.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaPartPayload.java
@@ -5,13 +5,25 @@
import java.util.List;
/**
- * Reference: {@code ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0}
+ * A payload file from a static delta.
+ *
+ * <p>The first byte is a compression byte: {@code 0} for none, {@code 'x'} for LZMA. The actual
+ * GVariant data begins right after.
+ *
+ * <p>Reference: {@code
+ * ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0}
+ *
+ * @param fileModes the {@link FileMode}s of the files generated by this delta payload.
+ * @param xattrs the {@link Xattr}s of the files generated by this delta payload.
+ * @param rawDataSource the data bytes used in the delta operations.
+ * @param operations the operations to apply during delta patching.
+ * @see DeltaSuperblock
*/
public record DeltaPartPayload(
List<FileMode> fileModes,
List<List<Xattr>> xattrs,
ByteString rawDataSource,
- ByteString operations) {
+ List<DeltaOperation> operations) {
private static final Decoder<DeltaPartPayload> DECODER =
Decoder.ofStructure(
@@ -19,8 +31,19 @@
Decoder.ofArray(FileMode.decoder()),
Decoder.ofArray(Decoder.ofArray(Xattr.decoder())),
ByteString.decoder(),
- ByteString.decoder());
+ ByteString.decoder().map(DeltaPartPayload::parseDeltaOperationList));
+ private static List<DeltaOperation> parseDeltaOperationList(ByteString byteString) {
+ return byteString.stream().map(DeltaOperation::valueOf).toList();
+ }
+
+ /**
+ * A file mode triple (UID, GID, and permission bits).
+ *
+ * @param uid
+ * @param gid
+ * @param mode
+ */
public record FileMode(int uid, int gid, int mode) {
private static final Decoder<FileMode> DECODER =
@@ -30,11 +53,24 @@
Decoder.ofInt().withByteOrder(ByteOrder.LITTLE_ENDIAN),
Decoder.ofInt().withByteOrder(ByteOrder.LITTLE_ENDIAN));
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<FileMode> decoder() {
return DECODER;
}
}
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * <p>FIXME: The first byte is actually a compression byte: {@code 0} for none, {@code 'x'} for
+ * LZMA.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<DeltaPartPayload> decoder() {
return DECODER;
}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java
index 2b09645..edcf2bf 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java
@@ -1,10 +1,27 @@
package eu.mulk.jgvariant.ostree;
import eu.mulk.jgvariant.core.Decoder;
+import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
import java.util.List;
-/** Reference: {@code ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT} */
+/**
+ * A static delta.
+ *
+ * <p>Reference: {@code ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT}
+ *
+ * @param metadata arbitrary user-supplied metadata.
+ * @param timestamp UNIX epoch seconds of when the commit was done.
+ * @param fromChecksum a (possibly {@link Checksum#isEmpty()}) reference to the starting commit.
+ * @param toChecksum a (non-{@link Checksum#isEmpty()}) reference to the end commit.
+ * @param commit the commit metadata of the end commit.
+ * @param dependencies a list of other {@link DeltaSuperblock}s that need to be applied before this
+ * one.
+ * @param entries a list of metadata on the {@link DeltaPartPayload}s that make up the delta.
+ * @param fallbacks a list of objects included in the delta as plain files that have to be fetched
+ * separately.
+ */
public record DeltaSuperblock(
Metadata metadata,
long timestamp,
@@ -15,11 +32,24 @@
List<DeltaMetaEntry> entries,
List<DeltaFallback> fallbacks) {
+ /**
+ * A specifier for another static delta.
+ *
+ * <p>Used to specify {@link DeltaSuperblock#dependencies()}.
+ *
+ * @param fromChecksum the {@link DeltaSuperblock#fromChecksum()} of the referenced delta.
+ * @param toChecksum the {@link DeltaSuperblock#toChecksum()} of the referenced delta.
+ */
public record DeltaName(Checksum fromChecksum, Checksum toChecksum) {
private static final Decoder<DeltaName> DECODER =
Decoder.ofStructure(DeltaName.class, Checksum.decoder(), Checksum.decoder());
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<DeltaName> decoder() {
return DECODER;
}
@@ -33,10 +63,28 @@
Checksum.decoder(),
Checksum.decoder(),
Commit.decoder(),
- Decoder.ofByteArray().map(x -> List.of()), // FIXME
+ Decoder.ofByteArray().map(DeltaSuperblock::parseDeltaNameList),
Decoder.ofArray(DeltaMetaEntry.decoder()),
Decoder.ofArray(DeltaFallback.decoder()));
+ private static List<DeltaName> parseDeltaNameList(byte[] bytes) {
+ var byteBuffer = ByteBuffer.wrap(bytes);
+ List<DeltaName> deltaNames = new ArrayList<>();
+
+ while (byteBuffer.hasRemaining()) {
+ var fromChecksum = Checksum.readFrom(byteBuffer);
+ var toChecksum = Checksum.readFrom(byteBuffer);
+ deltaNames.add(new DeltaName(fromChecksum, toChecksum));
+ }
+
+ return deltaNames;
+ }
+
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<DeltaSuperblock> decoder() {
return DECODER;
}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirMeta.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirMeta.java
index 3a9672d..35c5212 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirMeta.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirMeta.java
@@ -7,7 +7,19 @@
/**
* Permission bits and extended attributes for a directory.
*
+ * <p>Often comes in a pair with {@link DirTree}.
+ *
+ * <p>Referenced by {@link Commit#rootDirMetaChecksum()} and {@link
+ * DirTree.Directory#dirChecksum()}.
+ *
* <p>Reference: {@code ostree-core.h#OSTREE_DIRMETA_GVARIANT_STRING}
+ *
+ * @param uid the user ID that owns the directory.
+ * @param gid the group ID that owns the directory.
+ * @param mode the POSIX permission bits.
+ * @param xattrs POSIX extended attributes.
+ * @see DirTree
+ * @see ObjectType#DIR_META
*/
public record DirMeta(int uid, int gid, int mode, List<Xattr> xattrs) {
@@ -19,6 +31,11 @@
Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN),
Decoder.ofArray(Xattr.decoder()));
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<DirMeta> decoder() {
return DECODER;
}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirTree.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirTree.java
index 3a14abb..ef7d05f 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirTree.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirTree.java
@@ -7,24 +7,49 @@
/**
* Metadata describing files and directories of a file tree.
*
+ * <p>Often comes in a pair with {@link DirMeta}.
+ *
* <p>Referenced by {@link Commit#rootDirTreeChecksum()} and recursively by {@link
* Directory#treeChecksum()}.
*
* <p>Reference: {@code ostree-core.h#OSTREE_TREE_GVARIANT_STRING}
+ *
+ * @param files a list of files in the directory.
+ * @param directories a list of subdirectories of the directory.
+ * @see DirMeta
+ * @see ObjectType#DIR_TREE
*/
public record DirTree(List<File> files, List<Directory> directories) {
+ /**
+ * A file in a file tree.
+ *
+ * @param name the file name.
+ * @param checksum the checksum of the {@link ObjectType#FILE} object.
+ */
public record File(String name, Checksum checksum) {
private static final Decoder<File> DECODER =
Decoder.ofStructure(
File.class, Decoder.ofString(StandardCharsets.UTF_8), Checksum.decoder());
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<File> decoder() {
return DECODER;
}
}
+ /**
+ * A subdirectory in a file tree.
+ *
+ * @param name the name of the subdirectory.
+ * @param treeChecksum the checksum of the {@link DirTree} object.
+ * @param dirChecksum the checksum of the {@link DirMeta} object.
+ */
public record Directory(String name, Checksum treeChecksum, Checksum dirChecksum) {
private static final Decoder<Directory> DECODER =
@@ -34,6 +59,11 @@
Checksum.decoder(),
Checksum.decoder());
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<Directory> decoder() {
return DECODER;
}
@@ -43,6 +73,11 @@
Decoder.ofStructure(
DirTree.class, Decoder.ofArray(File.decoder()), Decoder.ofArray(Directory.decoder()));
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<DirTree> decoder() {
return DECODER;
}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/FileMeta.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/FileMeta.java
index 19e0677..2786ce9 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/FileMeta.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/FileMeta.java
@@ -7,7 +7,15 @@
/**
* Permission bits and extended attributes for a file.
*
+ * <p>Stored in a POSIX extended attribute on the corresponding {@link ObjectType#FILE} object in
+ * repositories in “bare-user” format.
+ *
* <p>Reference: {@code ostree-core.h#OSTREE_FILEMETA_GVARIANT_STRING}
+ *
+ * @param uid the user ID that owns the file.
+ * @param gid the group ID that owns the file.
+ * @param mode the POSIX permission bits.
+ * @param xattrs POSIX extended attributes.
*/
public record FileMeta(int uid, int gid, int mode, List<Xattr> xattrs) {
@@ -19,6 +27,11 @@
Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN),
Decoder.ofArray(Xattr.decoder()));
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<FileMeta> decoder() {
return DECODER;
}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java
index 8bb5255..fd8df28 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java
@@ -6,9 +6,11 @@
import java.util.Map;
/**
- * A wrapper for a list of metadata fields.
+ * A wrapper for a set of metadata fields.
*
* <p>Reference: (embedded in other data types)
+ *
+ * @param fields a set of metadata fields indexed by name.
*/
public record Metadata(Map<String, Variant> fields) {
@@ -16,6 +18,11 @@
Decoder.ofDictionary(Decoder.ofString(StandardCharsets.UTF_8), Decoder.ofVariant())
.map(Metadata::new);
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<Metadata> decoder() {
return DECODER;
}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ObjectType.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ObjectType.java
new file mode 100644
index 0000000..28371fc
--- /dev/null
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ObjectType.java
@@ -0,0 +1,80 @@
+package eu.mulk.jgvariant.ostree;
+
+import static org.apiguardian.api.API.Status.STABLE;
+
+import org.apiguardian.api.API;
+
+/**
+ * An object type as found in an OSTree repository.
+ *
+ * <p>Each object type has its own file extension.
+ *
+ * <p>In an OSTree repository, objects are located in a subfolder of the {@code /objects} folder
+ * based on their {@link Checksum}. The schema for looking up objects is {@code
+ * /objects/{checksumHead}/{checksumRest}.{fileExtension}} where:
+ *
+ * <dl>
+ * <dt>{@code {checksumHead}}
+ * <dd>the first two characters of {@link Checksum#hex()}
+ * <dt>{@code {checksumRest}}
+ * <dd>the substring of {@link Checksum#hex()} starting from the 3rd character
+ * <dt>{@code {fileExtension}}
+ * <dd>the {@link #fileExtension()} of the object type
+ * </dl>
+ */
+@API(status = STABLE)
+public enum ObjectType {
+ FILE((byte) 1, "file"),
+ DIR_TREE((byte) 2, "dirtree"),
+ DIR_META((byte) 3, "dirmeta"),
+ COMMIT((byte) 4, "commit"),
+ TOMBSTONE_COMMIT((byte) 5, "commit-tombstone"),
+ COMMIT_META((byte) 6, "commitmeta"),
+ PAYLOAD_LINK((byte) 7, "payload-link");
+
+ private final byte byteValue;
+ private final String fileExtension;
+
+ /**
+ * The serialized byte value.
+ *
+ * @return a byte representing this value in serialized GVariant structures.
+ */
+ public byte byteValue() {
+ return byteValue;
+ }
+
+ /**
+ * The file extension carried by files of this type.
+ *
+ * @return a file extension.
+ */
+ public String fileExtension() {
+ return fileExtension;
+ }
+
+ ObjectType(byte byteValue, String fileExtension) {
+ this.byteValue = byteValue;
+ this.fileExtension = fileExtension;
+ }
+
+ /**
+ * Returns the {@link ObjectType} corresponding to a serialized GVariant value.
+ *
+ * @param byteValue a serialized value as used in GVariant.
+ * @return the {@link ObjectType} corresponding to the serialized value.
+ * @throws IllegalArgumentException if the byte value is invalid.
+ */
+ public static ObjectType valueOf(byte byteValue) {
+ return switch (byteValue) {
+ case 1 -> FILE;
+ case 2 -> DIR_TREE;
+ case 3 -> DIR_META;
+ case 4 -> COMMIT;
+ case 5 -> TOMBSTONE_COMMIT;
+ case 6 -> COMMIT_META;
+ case 7 -> PAYLOAD_LINK;
+ default -> throw new IllegalArgumentException("invalid ObjectType: %d".formatted(byteValue));
+ };
+ }
+}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java
index 303e344..0077cb4 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java
@@ -2,6 +2,7 @@
import eu.mulk.jgvariant.core.Decoder;
import eu.mulk.jgvariant.core.Variant;
+import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
@@ -10,17 +11,33 @@
* A {@link DeltaSuperblock} signed with some sort of key.
*
* <p>Reference: {@code ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_SIGNED_FORMAT}
+ *
+ * @param magicNumber the value {@link #MAGIC}.
+ * @param superblock the {@link DeltaSuperblock}.
+ * @param signatures a list of signatures, indexed by type.
*/
public record SignedDelta(
- long magicNumber, ByteString superblock, Map<String, Variant> signatures) {
+ long magicNumber, DeltaSuperblock superblock, Map<String, Variant> signatures) {
+
+ /** The value of {@link #magicNumber()}. */
+ public static final long MAGIC = 0x4F53_5453_474E_4454L;
private static final Decoder<SignedDelta> DECODER =
Decoder.ofStructure(
SignedDelta.class,
Decoder.ofLong().withByteOrder(ByteOrder.BIG_ENDIAN),
- ByteString.decoder(),
+ ByteString.decoder().map(SignedDelta::decodeSuperblock),
Decoder.ofDictionary(Decoder.ofString(StandardCharsets.US_ASCII), Decoder.ofVariant()));
+ private static DeltaSuperblock decodeSuperblock(ByteString byteString) {
+ return DeltaSuperblock.decoder().decode(ByteBuffer.wrap(byteString.bytes()));
+ }
+
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<SignedDelta> decoder() {
return DECODER;
}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Summary.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Summary.java
index 8f4ddf6..bc94436 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Summary.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Summary.java
@@ -11,11 +11,29 @@
* <p>Stored as a file named {@code summary} in the OSTree repository root.
*
* <p>Reference: {@code ostree-core.h#OSTREE_SUMMARY_GVARIANT_STRING}
+ *
+ * @param entries an entry in the summary file.
+ * @param metadata additional keys and values contained in the summary.
*/
public record Summary(List<Entry> entries, Metadata metadata) {
+ /**
+ * An entry in the summary file of an OSTree repository, describing a ref.
+ *
+ * @param ref a ref name.
+ * @param value data describing the ref.
+ */
public record Entry(String ref, Value value) {
+ /**
+ * The value part of an entry in the summary file of an OSTree repository.
+ *
+ * <p>Describes the {@link Commit} currently named by the corresponding ref.
+ *
+ * @param size the size of the commit.
+ * @param checksum the checksum of the commit.
+ * @param metadata additional metadata describing the commit.
+ */
public record Value(long size, Checksum checksum, Metadata metadata) {
private static final Decoder<Value> DECODER =
@@ -25,6 +43,11 @@
Checksum.decoder(),
Metadata.decoder());
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<Value> decoder() {
return DECODER;
}
@@ -33,6 +56,11 @@
private static final Decoder<Entry> DECODER =
Decoder.ofStructure(Entry.class, Decoder.ofString(StandardCharsets.UTF_8), Value.decoder());
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<Entry> decoder() {
return DECODER;
}
@@ -41,6 +69,11 @@
private static final Decoder<Summary> DECODER =
Decoder.ofStructure(Summary.class, Decoder.ofArray(Entry.decoder()), Metadata.decoder());
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<Summary> decoder() {
return DECODER;
}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java
index 13bb432..efa48c3 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java
@@ -11,6 +11,8 @@
* <p>Stored as a file named {@code summary.sig} in the OSTree repository root.
*
* <p>Reference: {@code ostree-repo-static-delta-private.h#OSTREE_SUMMARY_SIG_GVARIANT_STRING}
+ *
+ * @param signatures a list of signatures, indexed by type.
*/
public record SummarySignature(Map<String, Variant> signatures) {
@@ -18,6 +20,11 @@
Decoder.ofDictionary(Decoder.ofString(StandardCharsets.UTF_8), Decoder.ofVariant())
.map(SummarySignature::new);
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<SummarySignature> decoder() {
return DECODER;
}
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Xattr.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Xattr.java
index 68628c4..7f05cf6 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Xattr.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Xattr.java
@@ -3,15 +3,27 @@
import eu.mulk.jgvariant.core.Decoder;
/**
- * Reference: (embedded in other data types, e.g. {@code
+ * A POSIX extended attribute of a file or directory.
+ *
+ * <p>Reference: (embedded in other data types, e.g. {@code
* ostree-core.h#OSTREE_DIRMETA_GVARIANT_STRING}, {@code
* ostree-core.h#OSTREE_FILEMETA_GVARIANT_STRING})
+ *
+ * @param name the name part of the extended attribute.
+ * @param value the value part of the extended attribute.
+ * @see DirMeta
+ * @see FileMeta
*/
public record Xattr(ByteString name, ByteString value) {
private static final Decoder<Xattr> DECODER =
Decoder.ofStructure(Xattr.class, ByteString.decoder(), ByteString.decoder());
+ /**
+ * Acquires a {@link Decoder} for the enclosing type.
+ *
+ * @return a possibly shared {@link Decoder}.
+ */
public static Decoder<Xattr> decoder() {
return DECODER;
}