jvgariant-ostree: Correctly deserialize delta operations.
Change-Id: Ic6659d7ea5e9411220571c33979e29471cec897e
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
index c1f2701..73cb67c 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaOperation.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaOperation.java
@@ -1,53 +1,85 @@
package eu.mulk.jgvariant.ostree;
-import static org.apiguardian.api.API.Status.STABLE;
-
-import org.apiguardian.api.API;
+import java.nio.ByteBuffer;
/** 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');
+public sealed interface DeltaOperation {
- private final byte byteValue;
+ record OpenSpliceAndCloseMeta(long offset, long size) implements DeltaOperation {}
- DeltaOperation(byte byteValue) {
- this.byteValue = byteValue;
- }
+ record OpenSpliceAndCloseReal(long offset, long size, long modeOffset, long xattrOffset)
+ implements DeltaOperation {}
- /**
- * The serialized byte value.
- *
- * @return a serialized byte value for use in GVariant structures.
- */
- public byte byteValue() {
- return byteValue;
- }
+ record Open(long size, long modeOffset, long xattrOffset) implements DeltaOperation {}
- /**
- * 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));
+ record Write(long offset, long size) implements DeltaOperation {}
+
+ record SetReadSource(long offset) implements DeltaOperation {}
+
+ record UnsetReadSource() implements DeltaOperation {}
+
+ record Close() implements DeltaOperation {}
+
+ record BsPatch(long offset, long size) implements DeltaOperation {}
+
+ static DeltaOperation readFrom(ByteBuffer byteBuffer, ObjectType objectType) {
+ byte opcode = byteBuffer.get();
+ return switch (DeltaOperationType.valueOf(opcode)) {
+ case OPEN_SPLICE_AND_CLOSE -> {
+ if (objectType == ObjectType.FILE || objectType == ObjectType.PAYLOAD_LINK) {
+ long modeOffset = readVarint64(byteBuffer);
+ long xattrOffset = readVarint64(byteBuffer);
+ long size = readVarint64(byteBuffer);
+ long offset = readVarint64(byteBuffer);
+ yield new OpenSpliceAndCloseReal(offset, size, modeOffset, xattrOffset);
+ } else {
+ long size = readVarint64(byteBuffer);
+ long offset = readVarint64(byteBuffer);
+ yield new OpenSpliceAndCloseMeta(offset, size);
+ }
+ }
+ case OPEN -> {
+ long modeOffset = readVarint64(byteBuffer);
+ long xattrOffset = readVarint64(byteBuffer);
+ long size = readVarint64(byteBuffer);
+ yield new Open(size, modeOffset, xattrOffset);
+ }
+ case WRITE -> {
+ long size = readVarint64(byteBuffer);
+ long offset = readVarint64(byteBuffer);
+ yield new Write(offset, size);
+ }
+ case SET_READ_SOURCE -> {
+ long offset = readVarint64(byteBuffer);
+ yield new SetReadSource(offset);
+ }
+ case UNSET_READ_SOURCE -> new UnsetReadSource();
+ case CLOSE -> new Close();
+ case BSPATCH -> {
+ long offset = readVarint64(byteBuffer);
+ long size = readVarint64(byteBuffer);
+ yield new BsPatch(offset, size);
+ }
};
}
+
+ /**
+ * Reads a Protobuf varint from a byte buffer.
+ *
+ * <p>Varint64 encoding is little-endian and works by using the lower 7 bits of each byte as the
+ * payload and the 0x80 bit as an indicator of whether the varint continues.
+ */
+ private static long readVarint64(ByteBuffer byteBuffer) {
+ long acc = 0L;
+
+ for (int i = 0; i < 10; ++i) {
+ long b = byteBuffer.get();
+ acc |= (b & 0x7F) << (i * 7);
+ if ((b & 0x80) == 0) {
+ break;
+ }
+ }
+
+ return acc;
+ }
}