diff options
Diffstat (limited to 'jgvariant-ostree')
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;    } | 
