blob: f89d414da8cfe6b0ab8a0cbab8e570abfae65071 [file] [log] [blame]
// SPDX-FileCopyrightText: © 2021 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
//
// SPDX-License-Identifier: LGPL-3.0-or-later
package eu.mulk.jgvariant.ostree;
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.util.ArrayList;
import java.util.List;
import org.tukaani.xz.XZInputStream;
/**
* A payload file from a static delta.
*
* <p>The first byte is a compression byte: {@code 0} for none, {@code 'x'} for LZMA. The actual
* GVariant data begins right after.
*
* <p>Reference: {@code
* ostree-repo-static-delta-private.h#OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0}
*
* @param fileModes the {@link FileMode}s of the files generated by this delta payload.
* @param xattrs the {@link Xattr}s of the files generated by this delta payload.
* @param rawDataSource the data bytes used in the delta operations.
* @param operations the operations to apply during delta patching.
* @see DeltaSuperblock
*/
public record DeltaPartPayload(
List<FileMode> fileModes,
List<List<Xattr>> xattrs,
ByteString rawDataSource,
List<DeltaOperation> operations) {
private static ByteBuffer preparse(ByteBuffer byteBuffer) {
byte compressionByte = byteBuffer.get(0);
var dataSlice = byteBuffer.slice(1, byteBuffer.limit() - 1);
return switch (compressionByte) {
case 0 -> dataSlice;
case (byte) 'x' -> {
try {
var dataBytes = new byte[dataSlice.limit()];
dataSlice.get(dataBytes);
var decompressingInputStream = new XZInputStream(new ByteArrayInputStream(dataBytes));
var decompressedOutputStream = new ByteArrayOutputStream();
decompressingInputStream.transferTo(decompressedOutputStream);
yield ByteBuffer.wrap(decompressedOutputStream.toByteArray());
} catch (IOException e) {
// impossible
throw new UncheckedIOException(e);
}
}
default -> throw new IllegalArgumentException(
"unrecognized compression byte '%d'".formatted(compressionByte));
};
}
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 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.BIG_ENDIAN),
Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN),
Decoder.ofInt().withByteOrder(ByteOrder.BIG_ENDIAN));
/**
* Acquires a {@link Decoder} for the enclosing type.
*
* @return a possibly shared {@link Decoder}.
*/
public static Decoder<FileMode> decoder() {
return DECODER;
}
}
/**
* Acquires a {@link Decoder} for the enclosing type.
*
* @return a possibly shared {@link 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);
}
}