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;
+  }
 }