Add jgvariant-ostree module.

Change-Id: Idf7bacad28d7cf65eb1ddd0994dcc2c2c2a7e18e
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java
new file mode 100644
index 0000000..1a85547
--- /dev/null
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java
@@ -0,0 +1,41 @@
+package eu.mulk.jgvariant.ostree;
+
+import eu.mulk.jgvariant.core.Decoder;
+import java.util.Arrays;
+import java.util.HexFormat;
+
+/**
+ * A wrapper for a {@code byte[]} that implements {@link #equals(Object)}, {@link #hashCode()}, and
+ * {@link #toString()} according to value semantics.
+ */
+public record ByteString(byte[] bytes) {
+
+  private static final Decoder<ByteString> DECODER = Decoder.ofByteArray().map(ByteString::new);
+
+  public static Decoder<ByteString> decoder() {
+    return DECODER;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return (o instanceof ByteString byteString) && Arrays.equals(bytes, byteString.bytes);
+  }
+
+  @Override
+  public int hashCode() {
+    return Arrays.hashCode(bytes);
+  }
+
+  @Override
+  public String toString() {
+    return "ByteString{hex=\"%s\"}".formatted(hex());
+  }
+
+  public String hex() {
+    return HexFormat.of().formatHex(bytes);
+  }
+
+  public static ByteString ofHex(String hex) {
+    return new ByteString(HexFormat.of().parseHex(hex));
+  }
+}