Document repository layout, add modified Base64 ByteString encoding.
Change-Id: I564db0e346346b608fa11527590e264c694fedaf
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 cf6e99a..bb8cbb6 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
@@ -2,6 +2,7 @@
import eu.mulk.jgvariant.core.Decoder;
import java.util.Arrays;
+import java.util.Base64;
import java.util.HexFormat;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@@ -62,6 +63,34 @@
}
/**
+ * Converts the contained byte array into modified Base64 (with {@code "/"} replaced with {@code
+ * "-"}).
+ *
+ * <p>Modified Base64 is Base64 with {@code "/"} replaced with {@code "_"}. It is used to address
+ * static deltas in an OSTree repository.
+ *
+ * <p>Useful for printing.
+ *
+ * @return a modified Base64 representation of the bytes making up this checksum.
+ */
+ public String modifiedBase64() {
+ return Base64.getEncoder().withoutPadding().encodeToString(bytes).replace('/', '_');
+ }
+
+ /**
+ * Parses a modified Base64 string into a {@link Checksum}.
+ *
+ * <p>Modified Base64 is Base64 with {@code "/"} replaced with {@code "_"}. It is used to address
+ * static deltas in an OSTree repository.
+ *
+ * @param mbase64 a hex string.
+ * @return a {@link Checksum} corresponding to the given modified Base64 string.
+ */
+ public static ByteString ofModifiedBase64(String mbase64) {
+ return new ByteString(Base64.getDecoder().decode(mbase64.replace('_', '/')));
+ }
+
+ /**
* Returns the number of bytes in the byte string.
*
* @return the number of bytes in the byte string.
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 ce5f9b0..94a728c 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
@@ -76,6 +76,34 @@
}
/**
+ * Converts the contained byte array into modified Base64 (with {@code "/"} replaced with {@code
+ * "-"}).
+ *
+ * <p>Modified Base64 is Base64 with {@code "/"} replaced with {@code "_"}. It is used to address
+ * static deltas in an OSTree repository.
+ *
+ * <p>Useful for printing.
+ *
+ * @return a modified Base64 representation of the bytes making up this checksum.
+ */
+ public String modifiedBase64() {
+ return byteString.modifiedBase64();
+ }
+
+ /**
+ * Parses a modified Base64 string into a {@link Checksum}.
+ *
+ * <p>Modified Base64 is Base64 with {@code "/"} replaced with {@code "_"}. It is used to address
+ * static deltas in an OSTree repository.
+ *
+ * @param mbase64 a hex string.
+ * @return a {@link Checksum} corresponding to the given modified Base64 string.
+ */
+ public static Checksum ofModifiedBase64(String mbase64) {
+ return new Checksum(ByteString.ofModifiedBase64(mbase64));
+ }
+
+ /**
* Reads a Checksum for a {@link ByteBuffer}.
*
* @param byteBuffer the byte buffer to read from.
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
index 28371fc..721d857 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ObjectType.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ObjectType.java
@@ -24,12 +24,56 @@
*/
@API(status = STABLE)
public enum ObjectType {
+
+ /**
+ * A regular file.
+ *
+ * <p>File ending: {@code .file}
+ */
FILE((byte) 1, "file"),
+
+ /**
+ * A serialized {@link DirTree} object.
+ *
+ * <p>File ending: {@code .dirtree}
+ */
DIR_TREE((byte) 2, "dirtree"),
+
+ /**
+ * A serialized {@link DirMeta} object.
+ *
+ * <p>File ending: {@code .dirmeta}
+ */
DIR_META((byte) 3, "dirmeta"),
+
+ /**
+ * A serialized {@link Commit} object.
+ *
+ * <p>File ending: {@code .commit}
+ */
COMMIT((byte) 4, "commit"),
+
+ /**
+ * A tombstone file standing in for a commit that was deleted.
+ *
+ * <p>File ending: {@code .commit-tombstone}
+ */
TOMBSTONE_COMMIT((byte) 5, "commit-tombstone"),
+
+ /**
+ * Detached metadata for a {@link Commit}.
+ *
+ * <p>Often goes together with a {@link #TOMBSTONE_COMMIT}.
+ *
+ * <p>File ending: {@code .commitmeta}
+ */
COMMIT_META((byte) 6, "commitmeta"),
+
+ /**
+ * A symlink to a {@link #FILE} that lives somewhere else.
+ *
+ * <p>File ending: {@code .payload-link}
+ */
PAYLOAD_LINK((byte) 7, "payload-link");
private final byte byteValue;
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/package-info.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/package-info.java
index c6a61f1..028f4cd 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/package-info.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/package-info.java
@@ -2,6 +2,183 @@
* Provides record classes describing the elements of <a
* href="https://ostreedev.github.io/ostree/">OSTree</a> repositories and factory methods to create
* {@link eu.mulk.jgvariant.core.Decoder} instances for them.
+ *
+ * <h2>OStree repository structure</h2>
+ *
+ * <p>An OSTree repository has the following layout:
+ *
+ * <table>
+ * <caption>OSTree repository layout</caption>
+ *
+ * <tr>
+ * <td>{@code config}</td>
+ * <td>
+ * <p>
+ * A plain text file that contains various settings. Among other things, this defines
+ * the <a href="https://ostreedev.github.io/ostree/formats/#the-archive-format">archive
+ * format</a> of the repository and whether files are compressed ({@code mode=archive-z2})
+ * or plain ({@code mode=bare}, {@code mode=bare-user}).
+ * </p>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@code summary}</td>
+ * <td>
+ * <p>
+ * A {@link eu.mulk.jgvariant.ostree.Summary} object containing information on what is
+ * available under {@code refs/}, {@code deltas/}, and {@code delta-indexes/}.
+ * </p>
+ *
+ * <p>This file may or may not exist and, if it exists, may or may not be up to date.</p>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@code refs/heads/{name...}}</td>
+ * <td>Plain-text files containing {@link eu.mulk.jgvariant.ostree.Checksum}s in hex format
+ * (see {@link eu.mulk.jgvariant.ostree.Checksum#ofHex}) referring to
+ * {@link eu.mulk.jgvariant.ostree.Commit} objects. See below for the layout of the
+ * {@code objects/} directory that this refers to.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@code objects/{checksumHead}/{checksumRest}.{fileExtension}}</td>
+ * <td>
+ * <p>
+ * Objects of various types as described by {@link eu.mulk.jgvariant.ostree.ObjectType}
+ * and keyed by {@link eu.mulk.jgvariant.ostree.Checksum}.
+ * </p>
+ *
+ * <p>
+ * Among other things, this is where you find {@link eu.mulk.jgvariant.ostree.Commit}
+ * ({@code .commit)}, {@link eu.mulk.jgvariant.ostree.DirTree} ({@code .dirtree}),
+ * and {@link eu.mulk.jgvariant.ostree.DirMeta} ({@code .dirmeta}) objects as well as
+ * plain ({@code .file}) or compressed files ({@code .filez}).
+ * </p>
+ *
+ * <p>
+ * Static delta information is not stored here, but in the {@code deltas/} and
+ * {@code delta-indexes/} directories (if available).
+ * </p>
+ *
+ * <p>
+ * The individual parts of the file path are defined as follows:
+ * </p>
+ *
+ * <dl>
+ * <dt>{@code {checksumHead}}
+ * <dd>
+ * the first two characters of {@link eu.mulk.jgvariant.ostree.Checksum#hex()}
+ * <dt>{@code {checksumRest}}
+ * <dd>
+ * the substring of {@link eu.mulk.jgvariant.ostree.Checksum#hex()} starting from the
+ * 3rd character
+ * <dt>{@code {fileExtension}}
+ * <dd>
+ * the {@link eu.mulk.jgvariant.ostree.ObjectType#fileExtension()} of the object type
+ * </dl>
+ * </td>
+ * </tr>
+ *
+ * <tr id="delta-superblock">
+ * <td>{@code deltas/{targetChecksumMbase64Head}/{targetChecksumMbase64Rest}/superblock}</td>
+ * <td>
+ * <p>
+ * A {@link eu.mulk.jgvariant.ostree.DeltaSuperblock} to get from nothing (an empty commit)
+ * to the commit named by the checksum encoded in the path.
+ * </p>
+ *
+ * <p>
+ * The individual parts of the file path are defined as follows:
+ * </p>
+ *
+ * <dl>
+ * <dt>{@code {checksumHead}}
+ * <dd>
+ * the first two characters of {@link eu.mulk.jgvariant.ostree.Checksum#modifiedBase64()}
+ * <dt>{@code {checksumRest}}
+ * <dd>
+ * the substring of {@link eu.mulk.jgvariant.ostree.Checksum#modifiedBase64()} starting
+ * from the 3rd character
+ * </dl>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@code deltas/{targetChecksumMbase64Head}/{targetChecksumMbase64Rest}/{digit...}}</td>
+ * <td>
+ * <p>
+ * A {@link eu.mulk.jgvariant.ostree.DeltaPartPayload} belonging to a delta that goes from
+ * nothing (an empty commit) to the commit named by the checksum encoded in the path.
+ * </p>
+ *
+ * <p>
+ * The individual parts of the file path are defined as follows:
+ * </p>
+ *
+ * <dl>
+ * <dt>{@code {checksumHead}}
+ * <dd>
+ * the first two characters of {@link eu.mulk.jgvariant.ostree.Checksum#modifiedBase64()}
+ * <dt>{@code {checksumRest}}
+ * <dd>
+ * the substring of {@link eu.mulk.jgvariant.ostree.Checksum#modifiedBase64()} starting
+ * from the 3rd character
+ * </dl>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@code deltas/{sourceChecksumMbase64Head}/{sourceChecksumMbase64Rest}-{targetChecksumMbase64}/superblock}</td>
+ * <td>
+ * <p>
+ * A {@link eu.mulk.jgvariant.ostree.DeltaSuperblock} to get from the source commit
+ * referenced by the first checksum encoded in the path to the target commit referenced
+ * in the second checksum encoded in the path.
+ * </p>
+ *
+ * <p>
+ * The individual parts of the file path are defined as follows:
+ * </p>
+ *
+ * <dl>
+ * <dt>{@code {checksumHead}}
+ * <dd>
+ * the first two characters of {@link eu.mulk.jgvariant.ostree.Checksum#modifiedBase64()}
+ * <dt>{@code {checksumRest}}
+ * <dd>
+ * the substring of {@link eu.mulk.jgvariant.ostree.Checksum#modifiedBase64()} starting
+ * from the 3rd character
+ * </dl>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@code deltas/{sourceChecksumMbase64Head}/{sourceChecksumMbase64Rest}-{targetChecksumMbase64}/{digit...}}</td>
+ * <td>
+ * <p>
+ * A {@link eu.mulk.jgvariant.ostree.DeltaPartPayload} belonging to a delta that goes from
+ * the source commit referenced by the first checksum encoded in the path to the target
+ * commit referenced in the second checksum encoded in the path.
+ * </p>
+ *
+ * <p>
+ * The individual parts of the file path are defined as follows:
+ * </p>
+ *
+ * <dl>
+ * <dt>{@code {checksumHead}}
+ * <dd>
+ * the first two characters of {@link eu.mulk.jgvariant.ostree.Checksum#modifiedBase64()}
+ * <dt>{@code {checksumRest}}
+ * <dd>
+ * the substring of {@link eu.mulk.jgvariant.ostree.Checksum#modifiedBase64()} starting
+ * from the 3rd character
+ * </dl>
+ * </td>
+ * </tr>
+ * </table>
*/
@API(status = Status.EXPERIMENTAL)
package eu.mulk.jgvariant.ostree;
diff --git a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/ByteStringTest.java b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/ByteStringTest.java
new file mode 100644
index 0000000..a5fa2b4
--- /dev/null
+++ b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/ByteStringTest.java
@@ -0,0 +1,70 @@
+package eu.mulk.jgvariant.ostree;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class ByteStringTest {
+
+ @Test
+ void testToModifiedBase64() {
+ assertEquals("MciDXVydLGaHpQCRyFFC0bLYU_9Bap+4G07jB1RRDVI", testByteString1.modifiedBase64());
+ }
+
+ @Test
+ void testOfModifiedBase64() {
+ assertEquals(
+ testByteString1,
+ ByteString.ofModifiedBase64("MciDXVydLGaHpQCRyFFC0bLYU_9Bap+4G07jB1RRDVI"));
+ }
+
+ @Test
+ void testToHex() {
+ assertEquals(
+ "31c8835d5c9d2c6687a50091c85142d1b2d853ff416a9fb81b4ee30754510d52", testByteString1.hex());
+ }
+
+ @Test
+ void testOfHex() {
+ assertEquals(
+ testByteString1,
+ ByteString.ofHex("31c8835d5c9d2c6687a50091c85142d1b2d853ff416a9fb81b4ee30754510d52"));
+ }
+
+ private static final ByteString testByteString1 =
+ new ByteString(
+ new byte[] {
+ (byte) 0x31,
+ (byte) 0xc8,
+ (byte) 0x83,
+ (byte) 0x5d,
+ (byte) 0x5c,
+ (byte) 0x9d,
+ (byte) 0x2c,
+ (byte) 0x66,
+ (byte) 0x87,
+ (byte) 0xa5,
+ (byte) 0x00,
+ (byte) 0x91,
+ (byte) 0xc8,
+ (byte) 0x51,
+ (byte) 0x42,
+ (byte) 0xd1,
+ (byte) 0xb2,
+ (byte) 0xd8,
+ (byte) 0x53,
+ (byte) 0xff,
+ (byte) 0x41,
+ (byte) 0x6a,
+ (byte) 0x9f,
+ (byte) 0xb8,
+ (byte) 0x1b,
+ (byte) 0x4e,
+ (byte) 0xe3,
+ (byte) 0x07,
+ (byte) 0x54,
+ (byte) 0x51,
+ (byte) 0x0d,
+ (byte) 0x52
+ });
+}
diff --git a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java
index db222f9..031b1cd 100644
--- a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java
+++ b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java
@@ -2,7 +2,6 @@
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import com.adelean.inject.resources.junit.jupiter.GivenBinaryResource;
import com.adelean.inject.resources.junit.jupiter.TestWithResources;
@@ -38,11 +37,6 @@
byte[] deltaPartPayloadBytes;
@Test
- void testTrivial() {
- assertTrue(true);
- }
-
- @Test
void testSummaryDecoder() {
var decoder = Summary.decoder();
var summary = decoder.decode(ByteBuffer.wrap(summaryBytes));