blob: 4e2587f7202748ccebeba7e6b51f90d6d4fa6d87 [file] [log] [blame]
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +01001package eu.mulk.jgvariant.ostree;
2
3import eu.mulk.jgvariant.core.Decoder;
Matthias Andreas Benkard50a626d2021-12-30 19:13:49 +01004import java.io.ByteArrayInputStream;
5import java.io.ByteArrayOutputStream;
6import java.io.IOException;
7import java.io.UncheckedIOException;
Matthias Andreas Benkarde74b8002021-12-30 18:52:10 +01008import java.nio.ByteBuffer;
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +01009import java.nio.ByteOrder;
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +010010import java.util.ArrayList;
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010011import java.util.List;
Matthias Andreas Benkard50a626d2021-12-30 19:13:49 +010012import org.tukaani.xz.XZInputStream;
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010013
14/**
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010015 * A payload file from a static delta.
16 *
17 * <p>The first byte is a compression byte: {@code 0} for none, {@code 'x'} for LZMA. The actual
18 * GVariant data begins right after.
19 *
20 * <p>Reference: {@code
21 * ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0}
22 *
23 * @param fileModes the {@link FileMode}s of the files generated by this delta payload.
24 * @param xattrs the {@link Xattr}s of the files generated by this delta payload.
25 * @param rawDataSource the data bytes used in the delta operations.
26 * @param operations the operations to apply during delta patching.
27 * @see DeltaSuperblock
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010028 */
29public record DeltaPartPayload(
30 List<FileMode> fileModes,
31 List<List<Xattr>> xattrs,
32 ByteString rawDataSource,
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010033 List<DeltaOperation> operations) {
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010034
Matthias Andreas Benkarde74b8002021-12-30 18:52:10 +010035 private static ByteBuffer preparse(ByteBuffer byteBuffer) {
36 byte compressionByte = byteBuffer.get(0);
Matthias Andreas Benkard50a626d2021-12-30 19:13:49 +010037 var dataSlice = byteBuffer.slice(1, byteBuffer.limit() - 1);
Matthias Andreas Benkarde74b8002021-12-30 18:52:10 +010038 return switch (compressionByte) {
Matthias Andreas Benkard50a626d2021-12-30 19:13:49 +010039 case 0 -> dataSlice;
40 case (byte) 'x' -> {
41 try {
42 var dataBytes = new byte[dataSlice.limit()];
43 dataSlice.get(dataBytes);
44 var decompressingInputStream = new XZInputStream(new ByteArrayInputStream(dataBytes));
45
46 var decompressedOutputStream = new ByteArrayOutputStream();
47 decompressingInputStream.transferTo(decompressedOutputStream);
48
49 yield ByteBuffer.wrap(decompressedOutputStream.toByteArray());
50 } catch (IOException e) {
51 // impossible
52 throw new UncheckedIOException(e);
53 }
54 }
Matthias Andreas Benkarde74b8002021-12-30 18:52:10 +010055 default -> throw new IllegalArgumentException(
56 "unrecognized compression byte '%d'".formatted(compressionByte));
57 };
58 }
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010059
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +010060 private static List<DeltaOperation> parseDeltaOperationList(
61 ByteString byteString, List<ObjectType> objectTypes) {
62 List<DeltaOperation> deltaOperations = new ArrayList<>();
63 var byteBuffer = ByteBuffer.wrap(byteString.bytes());
64 int objectIndex = 0;
65
66 while (byteBuffer.hasRemaining()) {
67 var currentOperation = DeltaOperation.readFrom(byteBuffer, objectTypes.get(objectIndex));
68 deltaOperations.add(currentOperation);
69 if (currentOperation instanceof DeltaOperation.Close
70 || currentOperation instanceof DeltaOperation.OpenSpliceAndCloseMeta
71 || currentOperation instanceof DeltaOperation.OpenSpliceAndCloseReal) {
72 ++objectIndex;
73 }
74 }
75
76 return deltaOperations;
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010077 }
78
79 /**
80 * A file mode triple (UID, GID, and permission bits).
81 *
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +010082 * @param uid the user ID that owns the file.
83 * @param gid the group ID that owns the file.
84 * @param mode the POSIX permission bits.
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010085 */
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010086 public record FileMode(int uid, int gid, int mode) {
87
88 private static final Decoder<FileMode> DECODER =
89 Decoder.ofStructure(
90 FileMode.class,
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +010091 Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN),
92 Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN),
93 Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN));
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010094
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010095 /**
96 * Acquires a {@link Decoder} for the enclosing type.
97 *
98 * @return a possibly shared {@link Decoder}.
99 */
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +0100100 public static Decoder<FileMode> decoder() {
101 return DECODER;
102 }
103 }
104
Matthias Andreas Benkard05114642021-12-29 21:51:29 +0100105 /**
106 * Acquires a {@link Decoder} for the enclosing type.
107 *
Matthias Andreas Benkard05114642021-12-29 21:51:29 +0100108 * @return a possibly shared {@link Decoder}.
109 */
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +0100110 public static Decoder<DeltaPartPayload> decoder(DeltaMetaEntry deltaMetaEntry) {
111 var objectTypes =
112 deltaMetaEntry.objects().stream().map(DeltaMetaEntry.DeltaObject::objectType).toList();
113 return Decoder.ofStructure(
114 DeltaPartPayload.class,
115 Decoder.ofArray(FileMode.decoder()),
116 Decoder.ofArray(Decoder.ofArray(Xattr.decoder())),
117 ByteString.decoder(),
118 ByteString.decoder()
119 .map(byteString -> parseDeltaOperationList(byteString, objectTypes)))
120 .contramap(DeltaPartPayload::preparse);
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +0100121 }
122}