diff options
author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2021-12-30 20:37:39 +0100 |
---|---|---|
committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2021-12-30 20:39:14 +0100 |
commit | c981cde61e1912e3d664d7a3e90d9e0efe050bd7 (patch) | |
tree | ce0f722c607f6dbe9354adeb0c003996c9da8ff9 | |
parent | 50a626db95a56160d8d08a6d0a6bb678b08b78d1 (diff) |
jvgariant-ostree: Correctly deserialize delta operations.
Change-Id: Ic6659d7ea5e9411220571c33979e29471cec897e
4 files changed, 160 insertions, 66 deletions
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'); - - private final byte byteValue; - - DeltaOperation(byte byteValue) { - this.byteValue = byteValue; - } +public sealed interface DeltaOperation { - /** - * The serialized byte value. - * - * @return a serialized byte value for use in GVariant structures. - */ - public byte byteValue() { - return byteValue; + record OpenSpliceAndCloseMeta(long offset, long size) implements DeltaOperation {} + + record OpenSpliceAndCloseReal(long offset, long size, long modeOffset, long xattrOffset) + implements DeltaOperation {} + + record Open(long size, long modeOffset, long xattrOffset) implements DeltaOperation {} + + 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); + } + }; } /** - * Returns the {@link DeltaOperation} corresponding to a serialized GVariant value. + * Reads a Protobuf varint from a byte buffer. * - * @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. + * <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. */ - 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)); - }; + 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; } } diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaOperationType.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaOperationType.java new file mode 100644 index 0000000..ac2056c --- /dev/null +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaOperationType.java @@ -0,0 +1,47 @@ +package eu.mulk.jgvariant.ostree; + +enum DeltaOperationType { + 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; + + DeltaOperationType(byte byteValue) { + this.byteValue = byteValue; + } + + /** + * The serialized byte value. + * + * @return a serialized byte value for use in GVariant structures. + */ + byte byteValue() { + return byteValue; + } + + /** + * Returns the {@link DeltaOperationType} corresponding to a serialized GVariant value. + * + * @param byteValue a serialized value as used in GVariant. + * @return the {@link DeltaOperationType} corresponding to the serialized value. + * @throws IllegalArgumentException if the byte value is invalid. + */ + static DeltaOperationType 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 ed0f4ff..4e2587f 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 @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.ArrayList; import java.util.List; import org.tukaani.xz.XZInputStream; @@ -31,15 +32,6 @@ public record DeltaPartPayload( ByteString rawDataSource, List<DeltaOperation> operations) { - private static final Decoder<DeltaPartPayload> DECODER = - Decoder.ofStructure( - DeltaPartPayload.class, - Decoder.ofArray(FileMode.decoder()), - Decoder.ofArray(Decoder.ofArray(Xattr.decoder())), - ByteString.decoder(), - ByteString.decoder().map(DeltaPartPayload::parseDeltaOperationList)) - .contramap(DeltaPartPayload::preparse); - private static ByteBuffer preparse(ByteBuffer byteBuffer) { byte compressionByte = byteBuffer.get(0); var dataSlice = byteBuffer.slice(1, byteBuffer.limit() - 1); @@ -65,25 +57,40 @@ public record DeltaPartPayload( }; } - private static List<DeltaOperation> parseDeltaOperationList(ByteString byteString) { - return byteString.stream().map(DeltaOperation::valueOf).toList(); + private static List<DeltaOperation> parseDeltaOperationList( + ByteString byteString, List<ObjectType> objectTypes) { + List<DeltaOperation> deltaOperations = new ArrayList<>(); + var byteBuffer = ByteBuffer.wrap(byteString.bytes()); + int objectIndex = 0; + + while (byteBuffer.hasRemaining()) { + var currentOperation = DeltaOperation.readFrom(byteBuffer, objectTypes.get(objectIndex)); + deltaOperations.add(currentOperation); + if (currentOperation instanceof DeltaOperation.Close + || currentOperation instanceof DeltaOperation.OpenSpliceAndCloseMeta + || currentOperation instanceof DeltaOperation.OpenSpliceAndCloseReal) { + ++objectIndex; + } + } + + return deltaOperations; } /** * A file mode triple (UID, GID, and permission bits). * - * @param uid - * @param gid - * @param mode + * @param uid the user ID that owns the file. + * @param gid the group ID that owns the file. + * @param mode the POSIX permission bits. */ public record FileMode(int uid, int gid, int mode) { private static final Decoder<FileMode> DECODER = Decoder.ofStructure( FileMode.class, - Decoder.ofInt().withByteOrder(ByteOrder.LITTLE_ENDIAN), - Decoder.ofInt().withByteOrder(ByteOrder.LITTLE_ENDIAN), - Decoder.ofInt().withByteOrder(ByteOrder.LITTLE_ENDIAN)); + Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN), + Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN), + Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN)); /** * Acquires a {@link Decoder} for the enclosing type. @@ -98,12 +105,18 @@ public record DeltaPartPayload( /** * 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; + public static Decoder<DeltaPartPayload> decoder(DeltaMetaEntry deltaMetaEntry) { + var objectTypes = + deltaMetaEntry.objects().stream().map(DeltaMetaEntry.DeltaObject::objectType).toList(); + return Decoder.ofStructure( + DeltaPartPayload.class, + Decoder.ofArray(FileMode.decoder()), + Decoder.ofArray(Decoder.ofArray(Xattr.decoder())), + ByteString.decoder(), + ByteString.decoder() + .map(byteString -> parseDeltaOperationList(byteString, objectTypes))) + .contramap(DeltaPartPayload::preparse); } } 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 05da7ed..db222f9 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 @@ -11,7 +11,6 @@ import eu.mulk.jgvariant.core.Variant; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @TestWithResources @@ -94,11 +93,14 @@ class OstreeDecoderTest { System.out.println(deltaSuperblock); } - @Disabled("invalid: compression byte not taken into account") @Test void testPartPayloadDecoder() { - var decoder = DeltaPartPayload.decoder(); + var superblockDecoder = DeltaSuperblock.decoder(); + var superblock = superblockDecoder.decode(ByteBuffer.wrap(deltaSuperblockBytes)); + + var decoder = DeltaPartPayload.decoder(superblock.entries().get(0)); var deltaPartPayload = decoder.decode(ByteBuffer.wrap(deltaPartPayloadBytes)); + System.out.println(deltaPartPayload); } } |