From aa11d82fe887d7c625c8bd0e89e2947e448d8bb4 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Sun, 10 Dec 2023 09:20:48 +0100 Subject: Add Decoder#encode. Implements: - the encoding part of the GVariant specification - OSTree-specific encoding instructions for static deltas Untested. Change-Id: Idbfd6d7e92a9cdff7d8b138d0ecfa36d4f30eee4 --- .../java/eu/mulk/jgvariant/ostree/ByteString.java | 3 +- .../java/eu/mulk/jgvariant/ostree/Checksum.java | 3 +- .../eu/mulk/jgvariant/ostree/DeltaFallback.java | 2 +- .../eu/mulk/jgvariant/ostree/DeltaMetaEntry.java | 19 +++++++- .../eu/mulk/jgvariant/ostree/DeltaOperation.java | 56 ++++++++++++++++++++++ .../eu/mulk/jgvariant/ostree/DeltaPartPayload.java | 52 ++++++++++++++++---- .../eu/mulk/jgvariant/ostree/DeltaSuperblock.java | 45 ++++++++++++++++- .../java/eu/mulk/jgvariant/ostree/Metadata.java | 3 +- .../java/eu/mulk/jgvariant/ostree/SignedDelta.java | 13 +++-- .../eu/mulk/jgvariant/ostree/SummarySignature.java | 3 +- 10 files changed, 179 insertions(+), 20 deletions(-) (limited to 'jgvariant-ostree/src/main') 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 cfe3635..3bd8b25 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 @@ -20,7 +20,8 @@ import org.jetbrains.annotations.Nullable; */ public record ByteString(byte[] bytes) { - private static final Decoder DECODER = Decoder.ofByteArray().map(ByteString::new); + private static final Decoder DECODER = + Decoder.ofByteArray().map(ByteString::new, ByteString::bytes); /** * Returns a decoder for a {@code byte[]} that wraps the result in {@link ByteString}. 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 261e2be..829664e 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 @@ -17,7 +17,8 @@ public record Checksum(ByteString byteString) { private static final int SIZE = 32; - private static final Decoder DECODER = ByteString.decoder().map(Checksum::new); + private static final Decoder DECODER = + ByteString.decoder().map(Checksum::new, Checksum::byteString); public Checksum { if (byteString.size() == 0) { diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaFallback.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaFallback.java index 57c8fc5..08e0b8c 100644 --- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaFallback.java +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaFallback.java @@ -25,7 +25,7 @@ public record DeltaFallback( private static final Decoder DECODER = Decoder.ofStructure( DeltaFallback.class, - Decoder.ofByte().map(ObjectType::valueOf), + Decoder.ofByte().map(ObjectType::valueOf, ObjectType::byteValue), Checksum.decoder(), Decoder.ofLong(), Decoder.ofLong()); diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaMetaEntry.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaMetaEntry.java index 8c6fd19..2be0426 100644 --- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaMetaEntry.java +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaMetaEntry.java @@ -5,6 +5,7 @@ package eu.mulk.jgvariant.ostree; import eu.mulk.jgvariant.core.Decoder; +import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; @@ -38,7 +39,9 @@ public record DeltaMetaEntry( private static final Decoder DECODER = Decoder.ofStructure( - DeltaObject.class, Decoder.ofByte().map(ObjectType::valueOf), Checksum.decoder()); + DeltaObject.class, + Decoder.ofByte().map(ObjectType::valueOf, ObjectType::byteValue), + Checksum.decoder()); /** * Acquires a {@link Decoder} for the enclosing type. @@ -57,7 +60,19 @@ public record DeltaMetaEntry( Checksum.decoder(), Decoder.ofLong(), Decoder.ofLong(), - Decoder.ofByteArray().map(DeltaMetaEntry::parseObjectList)); + Decoder.ofByteArray() + .map(DeltaMetaEntry::parseObjectList, DeltaMetaEntry::serializeObjectList)); + + private static byte[] serializeObjectList(List deltaObjects) { + var output = new ByteArrayOutputStream(); + + for (var deltaObject : deltaObjects) { + output.write(deltaObject.objectType.byteValue()); + output.writeBytes(deltaObject.checksum.byteString().bytes()); + } + + return output.toByteArray(); + } private static List parseObjectList(byte[] bytes) { var byteBuffer = ByteBuffer.wrap(bytes); 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 6edf217..d753a48 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 @@ -4,6 +4,7 @@ package eu.mulk.jgvariant.ostree; +import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; /** An operation in a static delta. */ @@ -67,6 +68,42 @@ public sealed interface DeltaOperation { }; } + default void writeTo(ByteArrayOutputStream output) { + if (this instanceof OpenSpliceAndCloseMeta openSpliceAndCloseMeta) { + output.write(DeltaOperationType.OPEN_SPLICE_AND_CLOSE.byteValue()); + writeVarint64(output, openSpliceAndCloseMeta.offset); + writeVarint64(output, openSpliceAndCloseMeta.size); + } else if (this instanceof OpenSpliceAndCloseReal openSpliceAndCloseReal) { + output.write(DeltaOperationType.OPEN_SPLICE_AND_CLOSE.byteValue()); + writeVarint64(output, openSpliceAndCloseReal.modeOffset); + writeVarint64(output, openSpliceAndCloseReal.xattrOffset); + writeVarint64(output, openSpliceAndCloseReal.size); + writeVarint64(output, openSpliceAndCloseReal.offset); + } else if (this instanceof Open open) { + output.write(DeltaOperationType.OPEN.byteValue()); + writeVarint64(output, open.modeOffset); + writeVarint64(output, open.xattrOffset); + writeVarint64(output, open.size); + } else if (this instanceof Write write) { + output.write(DeltaOperationType.WRITE.byteValue()); + writeVarint64(output, write.size); + writeVarint64(output, write.offset); + } else if (this instanceof SetReadSource setReadSource) { + output.write(DeltaOperationType.SET_READ_SOURCE.byteValue()); + writeVarint64(output, setReadSource.offset); + } else if (this instanceof UnsetReadSource) { + output.write(DeltaOperationType.UNSET_READ_SOURCE.byteValue()); + } else if (this instanceof Close) { + output.write(DeltaOperationType.CLOSE.byteValue()); + } else if (this instanceof BsPatch bsPatch) { + output.write(DeltaOperationType.BSPATCH.byteValue()); + writeVarint64(output, bsPatch.offset); + writeVarint64(output, bsPatch.size); + } else { + throw new IllegalStateException("unrecognized delta operation: %s".formatted(this)); + } + } + /** * Reads a Protobuf varint from a byte buffer. * @@ -86,4 +123,23 @@ public sealed interface DeltaOperation { return acc; } + + /** + * Writes a Protobuf varint to an output stream. + * + * @see #readVarint64 + */ + private static void writeVarint64(ByteArrayOutputStream output, long value) { + while (value != 0) { + byte b = (byte) (value & 0x7F); + value >>= 7; + if (value != 0) { + b |= (byte) 0x80; + } + output.write(b); + if (value == 0) { + break; + } + } + } } 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 f89d414..31c192d 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 @@ -8,12 +8,14 @@ import eu.mulk.jgvariant.core.Decoder; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.channels.Channels; import java.util.ArrayList; import java.util.List; +import org.tukaani.xz.LZMA2Options; import org.tukaani.xz.XZInputStream; +import org.tukaani.xz.XZOutputStream; /** * A payload file from a static delta. @@ -36,7 +38,7 @@ public record DeltaPartPayload( ByteString rawDataSource, List operations) { - private static ByteBuffer preparse(ByteBuffer byteBuffer) { + private static ByteBuffer decompress(ByteBuffer byteBuffer) { byte compressionByte = byteBuffer.get(0); var dataSlice = byteBuffer.slice(1, byteBuffer.limit() - 1); return switch (compressionByte) { @@ -53,7 +55,7 @@ public record DeltaPartPayload( yield ByteBuffer.wrap(decompressedOutputStream.toByteArray()); } catch (IOException e) { // impossible - throw new UncheckedIOException(e); + throw new IllegalStateException(e); } } default -> throw new IllegalArgumentException( @@ -61,10 +63,42 @@ public record DeltaPartPayload( }; } + private static ByteBuffer compress(ByteBuffer dataSlice) { + var dataBytes = new byte[dataSlice.limit()]; + dataSlice.get(dataBytes); + var compressedOutputStream = new ByteArrayOutputStream(); + + byte compressionByte = 'x'; + compressedOutputStream.write(compressionByte); + + try (var compressingOutputStream = + new XZOutputStream(compressedOutputStream, new LZMA2Options()); + var compressingChannel = Channels.newChannel(compressingOutputStream)) { + compressingChannel.write(dataSlice); + compressingOutputStream.write(dataBytes); + } catch (IOException e) { + // impossible + throw new IllegalStateException(e); + } + + var compressedBytes = compressedOutputStream.toByteArray(); + return ByteBuffer.wrap(compressedBytes); + } + + private static byte[] serializeDeltaOperationList(List deltaOperations) { + var output = new ByteArrayOutputStream(); + + for (var currentOperation : deltaOperations) { + currentOperation.writeTo(output); + } + + return output.toByteArray(); + } + private static List parseDeltaOperationList( - ByteString byteString, List objectTypes) { + byte[] bytes, List objectTypes) { List deltaOperations = new ArrayList<>(); - var byteBuffer = ByteBuffer.wrap(byteString.bytes()); + var byteBuffer = ByteBuffer.wrap(bytes); int objectIndex = 0; while (byteBuffer.hasRemaining()) { @@ -119,8 +153,10 @@ public record DeltaPartPayload( Decoder.ofArray(FileMode.decoder()), Decoder.ofArray(Decoder.ofArray(Xattr.decoder())), ByteString.decoder(), - ByteString.decoder() - .map(byteString -> parseDeltaOperationList(byteString, objectTypes))) - .contramap(DeltaPartPayload::preparse); + Decoder.ofByteArray() + .map( + bytes -> parseDeltaOperationList(bytes, objectTypes), + deltaOperations -> serializeDeltaOperationList(deltaOperations))) + .contramap(DeltaPartPayload::decompress, DeltaPartPayload::compress); } } diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java index 50da203..9513fa0 100644 --- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java @@ -5,10 +5,16 @@ package eu.mulk.jgvariant.ostree; import eu.mulk.jgvariant.core.Decoder; +import eu.mulk.jgvariant.core.Signature; +import eu.mulk.jgvariant.core.Variant; +import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.text.ParseException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * A static delta. @@ -67,10 +73,34 @@ public record DeltaSuperblock( Checksum.decoder(), Checksum.decoder(), Commit.decoder(), - Decoder.ofByteArray().map(DeltaSuperblock::parseDeltaNameList), + Decoder.ofByteArray() + .map( + DeltaSuperblock::parseDeltaNameList, DeltaSuperblock::serializeDeltaNameList), Decoder.ofArray(DeltaMetaEntry.decoder()).withByteOrder(ByteOrder.LITTLE_ENDIAN), Decoder.ofArray(DeltaFallback.decoder()).withByteOrder(ByteOrder.LITTLE_ENDIAN)) - .map(DeltaSuperblock::byteSwappedIfBigEndian); + .map(DeltaSuperblock::byteSwappedIfBigEndian, DeltaSuperblock::withSpecifiedByteOrder); + + private DeltaSuperblock withSpecifiedByteOrder() { + Map extendedMetadataMap = new HashMap<>(metadata().fields()); + + try { + extendedMetadataMap.putIfAbsent( + "ostree.endianness", new Variant(Signature.parse("y"), (byte) 'l')); + } catch (ParseException e) { + // impossible + throw new IllegalStateException(e); + } + + return new DeltaSuperblock( + new Metadata(extendedMetadataMap), + timestamp, + fromChecksum, + toChecksum, + commit, + dependencies, + entries, + fallbacks); + } private DeltaSuperblock byteSwappedIfBigEndian() { // Fix up the endianness of the 'entries' and 'fallbacks' fields, which have @@ -97,6 +127,17 @@ public record DeltaSuperblock( fallbacks.stream().map(DeltaFallback::byteSwapped).toList()); } + private static byte[] serializeDeltaNameList(List deltaNames) { + var output = new ByteArrayOutputStream(); + + for (var deltaName : deltaNames) { + output.writeBytes(deltaName.fromChecksum().byteString().bytes()); + output.writeBytes(deltaName.toChecksum().byteString().bytes()); + } + + return output.toByteArray(); + } + private static List parseDeltaNameList(byte[] bytes) { var byteBuffer = ByteBuffer.wrap(bytes); List deltaNames = new ArrayList<>(); diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java index 62f0331..f485be1 100644 --- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java @@ -20,7 +20,8 @@ import java.util.Map; public record Metadata(Map fields) { private static final Decoder DECODER = - Decoder.ofDictionary(Decoder.ofString(UTF_8), Decoder.ofVariant()).map(Metadata::new); + Decoder.ofDictionary(Decoder.ofString(UTF_8), Decoder.ofVariant()) + .map(Metadata::new, Metadata::fields); /** * Acquires a {@link Decoder} for the enclosing type. diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java index 827d5e4..1e1e58e 100644 --- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java @@ -31,11 +31,18 @@ public record SignedDelta( Decoder.ofStructure( SignedDelta.class, Decoder.ofLong().withByteOrder(ByteOrder.BIG_ENDIAN), - ByteString.decoder().map(SignedDelta::decodeSuperblock), + Decoder.ofByteArray().map(SignedDelta::decodeSuperblock, SignedDelta::encodeSuperblock), Decoder.ofDictionary(Decoder.ofString(US_ASCII), Decoder.ofVariant())); - private static DeltaSuperblock decodeSuperblock(ByteString byteString) { - return DeltaSuperblock.decoder().decode(ByteBuffer.wrap(byteString.bytes())); + private static DeltaSuperblock decodeSuperblock(byte[] bytes) { + return DeltaSuperblock.decoder().decode(ByteBuffer.wrap(bytes)); + } + + private static byte[] encodeSuperblock(DeltaSuperblock deltaSuperblock) { + var byteBuffer = DeltaSuperblock.decoder().encode(deltaSuperblock); + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return bytes; } /** diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java index 3c88759..5834c91 100644 --- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java +++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java @@ -22,7 +22,8 @@ import java.util.Map; public record SummarySignature(Map signatures) { private static final Decoder DECODER = - Decoder.ofDictionary(Decoder.ofString(UTF_8), Decoder.ofVariant()).map(SummarySignature::new); + Decoder.ofDictionary(Decoder.ofString(UTF_8), Decoder.ofVariant()) + .map(SummarySignature::new, SummarySignature::signatures); /** * Acquires a {@link Decoder} for the enclosing type. -- cgit v1.2.3