diff options
Diffstat (limited to 'jgvariant-ostree/src/main/java')
17 files changed, 554 insertions, 18 deletions
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 @@ package eu.mulk.jgvariant.ostree; 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 @@ public record ByteString(byte[] bytes) { 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; } + /** + * 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 bytes.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 @@ import java.util.List; * <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 @@ public record Commit( 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 @@ public record Commit( 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 @@ import java.nio.ByteOrder; /** * 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 @@ public record DeltaMetaEntry( 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.nio.ByteOrder; 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 @@ public record DeltaPartPayload( 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 @@ public record DeltaPartPayload( 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 @@ public record DeltaSuperblock( 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 @@ public record DeltaSuperblock( 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 @@ import java.util.List; /** * 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 @@ public record DirMeta(int uid, int gid, int mode, List<Xattr> xattrs) { 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 @@ import java.util.List; /** * 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 @@ public record DirTree(List<File> files, List<Directory> directories) { 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 @@ public record DirTree(List<File> files, List<Directory> directories) { 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 @@ import java.util.List; /** * 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 @@ public record FileMeta(int uid, int gid, int mode, List<Xattr> xattrs) { 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.nio.charset.StandardCharsets; 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 @@ public record Metadata(Map<String, Variant> fields) { 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 @@ package eu.mulk.jgvariant.ostree; 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 @@ import java.util.Map; * 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 @@ import java.util.List; * <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 @@ public record Summary(List<Entry> entries, Metadata metadata) { 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 @@ public record Summary(List<Entry> entries, Metadata metadata) { 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 @@ public record Summary(List<Entry> entries, Metadata metadata) { 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 @@ import java.util.Map; * <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 @@ public record SummarySignature(Map<String, Variant> signatures) { 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 @@ package eu.mulk.jgvariant.ostree; 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; } |