blob: ccd563036c51332c48a3870ebdc2cbf6e8ee4b9b [file] [log] [blame]
Matthias Andreas Benkardb5d657a2022-02-03 21:14:30 +01001// SPDX-FileCopyrightText: © 2021 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +01005package eu.mulk.jgvariant.ostree;
6
7import eu.mulk.jgvariant.core.Decoder;
Matthias Andreas Benkard50a626d2021-12-30 19:13:49 +01008import java.io.ByteArrayInputStream;
9import java.io.ByteArrayOutputStream;
10import java.io.IOException;
Matthias Andreas Benkarde74b8002021-12-30 18:52:10 +010011import java.nio.ByteBuffer;
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010012import java.nio.ByteOrder;
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010013import java.nio.channels.Channels;
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +010014import java.util.ArrayList;
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010015import java.util.List;
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010016import org.tukaani.xz.LZMA2Options;
Matthias Andreas Benkard50a626d2021-12-30 19:13:49 +010017import org.tukaani.xz.XZInputStream;
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010018import org.tukaani.xz.XZOutputStream;
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010019
20/**
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010021 * A payload file from a static delta.
22 *
23 * <p>The first byte is a compression byte: {@code 0} for none, {@code 'x'} for LZMA. The actual
24 * GVariant data begins right after.
25 *
26 * <p>Reference: {@code
27 * ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0}
28 *
29 * @param fileModes the {@link FileMode}s of the files generated by this delta payload.
30 * @param xattrs the {@link Xattr}s of the files generated by this delta payload.
31 * @param rawDataSource the data bytes used in the delta operations.
32 * @param operations the operations to apply during delta patching.
33 * @see DeltaSuperblock
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010034 */
35public record DeltaPartPayload(
36 List<FileMode> fileModes,
37 List<List<Xattr>> xattrs,
38 ByteString rawDataSource,
Matthias Andreas Benkard05114642021-12-29 21:51:29 +010039 List<DeltaOperation> operations) {
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010040
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010041 private static ByteBuffer decompress(ByteBuffer byteBuffer) {
Matthias Andreas Benkarde74b8002021-12-30 18:52:10 +010042 byte compressionByte = byteBuffer.get(0);
Matthias Andreas Benkard50a626d2021-12-30 19:13:49 +010043 var dataSlice = byteBuffer.slice(1, byteBuffer.limit() - 1);
Matthias Andreas Benkarde74b8002021-12-30 18:52:10 +010044 return switch (compressionByte) {
Matthias Andreas Benkard50a626d2021-12-30 19:13:49 +010045 case 0 -> dataSlice;
46 case (byte) 'x' -> {
47 try {
48 var dataBytes = new byte[dataSlice.limit()];
49 dataSlice.get(dataBytes);
50 var decompressingInputStream = new XZInputStream(new ByteArrayInputStream(dataBytes));
51
52 var decompressedOutputStream = new ByteArrayOutputStream();
53 decompressingInputStream.transferTo(decompressedOutputStream);
54
55 yield ByteBuffer.wrap(decompressedOutputStream.toByteArray());
56 } catch (IOException e) {
57 // impossible
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010058 throw new IllegalStateException(e);
Matthias Andreas Benkard50a626d2021-12-30 19:13:49 +010059 }
60 }
Matthias Andreas Benkarde74b8002021-12-30 18:52:10 +010061 default -> throw new IllegalArgumentException(
62 "unrecognized compression byte '%d'".formatted(compressionByte));
63 };
64 }
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010065
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010066 private static ByteBuffer compress(ByteBuffer dataSlice) {
67 var dataBytes = new byte[dataSlice.limit()];
68 dataSlice.get(dataBytes);
69 var compressedOutputStream = new ByteArrayOutputStream();
70
71 byte compressionByte = 'x';
72 compressedOutputStream.write(compressionByte);
73
74 try (var compressingOutputStream =
75 new XZOutputStream(compressedOutputStream, new LZMA2Options());
76 var compressingChannel = Channels.newChannel(compressingOutputStream)) {
77 compressingChannel.write(dataSlice);
78 compressingOutputStream.write(dataBytes);
79 } catch (IOException e) {
80 // impossible
81 throw new IllegalStateException(e);
82 }
83
84 var compressedBytes = compressedOutputStream.toByteArray();
85 return ByteBuffer.wrap(compressedBytes);
86 }
87
88 private static byte[] serializeDeltaOperationList(List<DeltaOperation> deltaOperations) {
89 var output = new ByteArrayOutputStream();
90
91 for (var currentOperation : deltaOperations) {
92 currentOperation.writeTo(output);
93 }
94
95 return output.toByteArray();
96 }
97
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +010098 private static List<DeltaOperation> parseDeltaOperationList(
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +010099 byte[] bytes, List<ObjectType> objectTypes) {
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +0100100 List<DeltaOperation> deltaOperations = new ArrayList<>();
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +0100101 var byteBuffer = ByteBuffer.wrap(bytes);
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +0100102 int objectIndex = 0;
103
104 while (byteBuffer.hasRemaining()) {
105 var currentOperation = DeltaOperation.readFrom(byteBuffer, objectTypes.get(objectIndex));
106 deltaOperations.add(currentOperation);
107 if (currentOperation instanceof DeltaOperation.Close
108 || currentOperation instanceof DeltaOperation.OpenSpliceAndCloseMeta
109 || currentOperation instanceof DeltaOperation.OpenSpliceAndCloseReal) {
110 ++objectIndex;
111 }
112 }
113
114 return deltaOperations;
Matthias Andreas Benkard05114642021-12-29 21:51:29 +0100115 }
116
117 /**
118 * A file mode triple (UID, GID, and permission bits).
119 *
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +0100120 * @param uid the user ID that owns the file.
121 * @param gid the group ID that owns the file.
122 * @param mode the POSIX permission bits.
Matthias Andreas Benkard05114642021-12-29 21:51:29 +0100123 */
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +0100124 public record FileMode(int uid, int gid, int mode) {
125
126 private static final Decoder<FileMode> DECODER =
127 Decoder.ofStructure(
128 FileMode.class,
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +0100129 Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN),
130 Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN),
131 Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN));
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +0100132
Matthias Andreas Benkard05114642021-12-29 21:51:29 +0100133 /**
134 * Acquires a {@link Decoder} for the enclosing type.
135 *
136 * @return a possibly shared {@link Decoder}.
137 */
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +0100138 public static Decoder<FileMode> decoder() {
139 return DECODER;
140 }
141 }
142
Matthias Andreas Benkard05114642021-12-29 21:51:29 +0100143 /**
144 * Acquires a {@link Decoder} for the enclosing type.
145 *
Matthias Andreas Benkard05114642021-12-29 21:51:29 +0100146 * @return a possibly shared {@link Decoder}.
147 */
Matthias Andreas Benkardc981cde2021-12-30 20:37:39 +0100148 public static Decoder<DeltaPartPayload> decoder(DeltaMetaEntry deltaMetaEntry) {
149 var objectTypes =
150 deltaMetaEntry.objects().stream().map(DeltaMetaEntry.DeltaObject::objectType).toList();
151 return Decoder.ofStructure(
152 DeltaPartPayload.class,
153 Decoder.ofArray(FileMode.decoder()),
154 Decoder.ofArray(Decoder.ofArray(Xattr.decoder())),
155 ByteString.decoder(),
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +0100156 Decoder.ofByteArray()
157 .map(
158 bytes -> parseDeltaOperationList(bytes, objectTypes),
Matthias Andreas Benkardc442ebe2023-12-10 17:58:38 +0100159 DeltaPartPayload::serializeDeltaOperationList))
Matthias Andreas Benkardaa11d822023-12-10 09:20:48 +0100160 .contramap(DeltaPartPayload::decompress, DeltaPartPayload::compress);
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +0100161 }
162}